通知付きのWPI UIロック検出器





ご挨拶!



プログラマーは、何らかの理由でUIをブロックするアプリケーションに遭遇したと思います。 このようなロックには、サービスへの同期要求、UIスレッドでの長い操作のパフォーマンスなど、多くの理由があります。

最良の場合、UIロックにつながるコードのセクションを書き換え/修正する必要がありますが、これはさまざまな理由で常に可能とは限らないため、最小限のコストで問題を解決できる何らかの特効薬を入手したいと考えています。

そのようなプールの1つについて説明します。



カットの下の詳細。



UIがブロックされていると判断します



実際、ロックされたUIが2つのカウンターを開始するという単純な決定に還元されると判断します。 最初のカウンターは、アプリケーションのメインスレッドで機能し、各操作にタイムスタンプを付けます。 2番目のカウンターはバックグラウンドスレッドで実行され、現在の時刻と最初のカウンターで設定された時刻の差を計算します。 時間の差が特定の制限を超えると、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(); }
      
      







ウィンドウデリゲートは、通知ウィンドウに対してより柔軟なアプローチを可能にするために必要です。

この記事の別のスレッドでのウィンドウの作成の詳細については、別のスレッドでのWPFウィンドウの起動をご覧ください。



結果

提案された解決策は、すべての人に完全に適合する同じ特効薬ではないことに注意する必要があります。 多くの場合、何らかの理由でそのようなソリューションを適用することは不可能だと確信しています。

私のデモプロジェクトですべてがどのように機能するかを見ることができます: yadi.sk/d/WeIG1JvEhC2Hw



みんなありがとう!



All Articles