Детектор блокировок UI в WPF c нотификацией

от автора

Приветствую!

Думаю что каждому из программистов попадалось приложение которое по тем или иным причинам блокировало UI. Причин у таких блокировок может быть множество, такие как: синхронные запросы к сервисам, выполнение долгих операций в UI треде и прочее.
В самом лучшем случае участки кода приводящие к блокировкам UI должны быть переписаны / исправлены, но это не всегда возможно по разным причинам и соответственно хочется получить некую серебряную пулю, которая сможет решить проблему с минимальной стоимостью.
О одной такой пуле и пойдет речь.

Подробности под катом.

Определяем что UI заблокирован

Собственно определение того что заблокирован UI сводится к простому решению запустить два счетчика. Первый счетчик работает в главном треде приложения и ставит временные метки при каждом срабатывании. Второй счетчик работает в фоновом треде и вычисляет разницу между текущим временем и временем установленным первым счетчиком. Если разница между временами превышает определенный лимит, выбрасывается событие о том что UI заблокирован и наоборот, если UI уже не заблокирован выбрасываем событие о том что приложение ожило.
Делается это так:

internal class BlockDetector {     bool _isBusy;      private const int FreezeTimeLimit = 400;      private readonly DispatcherTimer _foregroundTimer;      private readonly Timer _backgroundTimer;      private DateTime _lastForegroundTimerTickTime;      public event Action UIBlocked;      public event Action UIReleased;      public BlockDetector()     {         _foregroundTimer = new DispatcherTimer{ Interval = TimeSpan.FromMilliseconds(FreezeTimeLimit / 2) };         _foregroundTimer.Tick += ForegroundTimerTick;          _backgroundTimer = new Timer(BackgroundTimerTick, null, FreezeTimeLimit, Timeout.Infinite);     }      private void BackgroundTimerTick(object someObject)     {         var totalMilliseconds = (DateTime.Now - _lastForegroundTimerTickTime).TotalMilliseconds;         if (totalMilliseconds > FreezeTimeLimit && _isBusy == false)         {             _isBusy = true;             Dispatcher.CurrentDispatcher.Invoke(() => UIBlocked()); ;         }         else         {             if (totalMilliseconds < FreezeTimeLimit && _isBusy)             {                 _isBusy = false;                 Dispatcher.CurrentDispatcher.Invoke(() => UIReleased()); ;             }          }         _backgroundTimer.Change(FreezeTimeLimit, Timeout.Infinite);     }      private void ForegroundTimerTick(object sender, EventArgs e)     {         _lastForegroundTimerTickTime = DateTime.Now;     }      public void Start()     {         _foregroundTimer.Start();     }      public void Stop()     {         _foregroundTimer.Stop();         _backgroundTimer.Dispose();     } } 

Сообщение о блокировке UI

Для того чтобы показать пользователю сообщение о том что приложение работает, подписываемся на события от класса BlockDetector и показываем новое окно с сообщением о заблокированном UI.

WPF разрешает создавать несколько UI тредов. Делается это так:

private void ShowNotify() {     var thread = new Thread((ThreadStart)delegate     {         // получаем ссылку на текущий диспетчер         _threadDispacher = Dispatcher.CurrentDispatcher;                  SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(_threadDispacher));                  // создаем новое окно           _notifyWindow = _createWindowDelegate.Invoke();                  // подписываем на событие закрытия окна и завершаем текущий тред         _notifyWindow.Closed += (sender,e) => _threadDispacher.BeginInvokeShutdown(DispatcherPriority.Background);         _notifyWindow.Show();                  // запускаем обработку сообщений Windows для треда         Dispatcher.Run();     });      thread.SetApartmentState(ApartmentState.STA);     thread.IsBackground = true;     thread.Start(); } 

Делегат на создание окна нужен для того чтобы иметь возможность более гибкого подхода к окну нотификации.
Более подробно прочитать о создании окна в отдельном треде можно почитать в этой статье Launching a WPF Window in a Separate Thread

Результат
Необходимо оговорится что предложенное решение не является той самой серебряной пулей, которая подойдет абсолютно всем. Уверен, что в целом ряде случаев применить такое решение окажется невозможным по тем или иным причинам.
Посмотреть как это все работает можно на подготовленном мной демо-проекте: yadi.sk/d/WeIG1JvEhC2Hw

Всем спасибо!

ссылка на оригинал статьи http://habrahabr.ru/post/260029/