WPI UI Lock Detektor mit Benachrichtigung

  • Tutorial


Grüße

Ich denke, dass jeder der Programmierer auf eine Anwendung gestoßen ist, die aus dem einen oder anderen Grund die Benutzeroberfläche blockiert hat. Es kann viele Gründe für solche Sperren geben, z. B .: synchrone Anforderungen an Dienste, Leistung langer Operationen im UI-Thread und so weiter.
Im besten Fall sollten Codeabschnitte, die zu UI-Sperren führen, umgeschrieben / korrigiert werden. Dies ist jedoch aus verschiedenen Gründen nicht immer möglich, und dementsprechend möchte ich eine Art Wunderwaffe erhalten, mit der das Problem mit minimalen Kosten gelöst werden kann.
Ein solcher Pool wird diskutiert.

Details unter dem Schnitt.

Wir stellen fest, dass die Benutzeroberfläche gesperrt ist

Feststellen, dass eine gesperrte Benutzeroberfläche auf eine einfache Entscheidung zum Starten von zwei Zählern reduziert ist. Der erste Zähler arbeitet im Hauptthread der Anwendung und setzt bei jeder Operation Zeitstempel. Der zweite Zähler wird im Hintergrund-Thread ausgeführt und berechnet die Differenz zwischen der aktuellen Zeit und der vom ersten Zähler festgelegten Zeit. Wenn die Zeitdifferenz einen bestimmten Grenzwert überschreitet, wird ein Ereignis ausgelöst, bei dem die Benutzeroberfläche blockiert ist, und umgekehrt, wenn die Benutzeroberfläche nicht bereits blockiert ist, wird ein Ereignis ausgelöst, bei dem die Anwendung zum Leben erweckt wurde.
Es ist so gemacht:
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-Blockierungsnachricht

Damit der Benutzer eine Nachricht erhält, dass die Anwendung ausgeführt wird, abonnieren wir Ereignisse aus der BlockDetector-Klasse und zeigen ein neues Fenster mit einer Nachricht über die blockierte UI an.

Mit WPF können Sie mehrere UI-Threads erstellen. Es ist so gemacht:
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();
}


Der Fensterdelegierte wird benötigt, um flexibler mit dem Benachrichtigungsfenster umgehen zu können.
Weitere Informationen zum Erstellen eines Fensters in einem separaten Thread finden Sie in diesem Artikel. Starten eines WPF-Fensters in einem separaten Thread

Ergebnis
Es muss festgelegt werden, dass es sich bei der vorgeschlagenen Lösung nicht um die gleiche Standardlösung handelt, die für absolut jeden geeignet ist. Ich bin sicher, dass es in einigen Fällen aus dem einen oder anderen Grund unmöglich sein wird, eine solche Lösung anzuwenden.
Sie können sehen, wie alles in meinem Demo-Projekt funktioniert : yadi.sk/d/WeIG1JvEhC2Hw

Vielen Dank an alle!

Jetzt auch beliebt: