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

7946c3b443b741e3b79c75b04a4339f2.pngПриветствую!

Думаю что каждому из программистов попадалось приложение которое по тем или иным причинам блокировало 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

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

© Habrahabr.ru