web-dev-qa-db-ger.com

WPF Dispatcher.BeginInvoke und UI/Hintergrund-Threads

Ich denke, ich brauche einige Klarstellungen bezüglich der Verwendung von WPFs Dispatcher.Invoke und Dispatcher.BeginInvoke.

Angenommen, ich habe einige lange laufende "Arbeits" -Codes wie diese, die auf Knopfdruck in einer einfachen WPF-Anwendung aufgerufen werden:

longWorkTextBox.Text = "Ready For Work!";
Action workAction = delegate
    {
    Console.WriteLine("Starting Work Action");
    int i = int.MaxValue;
    while (i > 0)
        i--;
    Console.WriteLine("Ending Work Action");
    longWorkTextBox.Text = "Work Complete";
    };
longWorkTextBox.Dispatcher.BeginInvoke(DispatcherPriority.Background, workAction);

Dieser Code blockiert meine Benutzeroberfläche, während die workAction ausgeführt wird. Dies liegt daran, dass Dispatcher-Aufrufe immer auf dem UI-Thread ausgeführt werden, oder?

Angenommen, Was ist die beste Methode, um meinen Dispatcher so zu konfigurieren, dass workAction in einem separaten Thread von meiner Benutzeroberfläche ausgeführt wird? Ich weiß, dass ich BackgroundWorker meiner workAction hinzufügen kann um zu verhindern, dass meine Benutzeroberfläche gesperrt wird:

longWorkTextBox.Text = "Ready For Work!";
Action workAction = delegate
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += delegate
    {
        Console.WriteLine("Starting Slow Work");
        int i = int.MaxValue;
        while (i > 0)
        i--;
        Console.WriteLine("Ending Work Action");
    };
    worker.RunWorkerCompleted += delegate
    {
        longWorkTextBox.Text = "Work Complete";
    };
    worker.RunWorkerAsync();
 };
 longWorkTextBox.Dispatcher.BeginInvoke(DispatcherPriority.Background, workAction);

Gibt es neben dem BackgroundWorker noch elegantere Methoden? Ich habe immer gehört, dass der BackgroundWorker schrullig ist, also bin ich neugierig auf einige Alternativen.

18
Matthew Ruston

Ich denke ehrlich, dass die BackgroundWorker die eleganteste Lösung dafür ist. Ich kann mir keinen einfacheren Weg vorstellen.

25
Charlie

Ich mag BackgroundWorker auch nicht ... Eine einfache Alternative kann so etwas wie sein:

using System;
using System.Threading;
using System.Windows;

namespace Sample
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        protected override void OnSourceInitialized(EventArgs e)
        {
             base.OnSourceInitialized(e);
             longWorkTextBox.Text = "Ready For Work!";
       }

        private void startButton_Click(object sender, RoutedEventArgs e)
        {
            new Thread(Work).Start();
        }

        void Work()
        {
            longWorkTextBox.Dispatcher.BeginInvoke((Action)(() => { longWorkTextBox.Text = "Working..."; }));
            Console.WriteLine("Starting Work Action");
            int i = int.MaxValue;
            while (i > 0)
                i--;
            Console.WriteLine("Ending Work Action");
            longWorkTextBox.Dispatcher.BeginInvoke((Action)(() => { longWorkTextBox.Text = "Work Complete"; }));
        }
    }
}

Einfach nicht?

4
italianogrosso

Charlies Antwort ist genau das, wonach du suchst.

Wenn es möglich ist, können Sie jedoch prüfen, ob Sie Ihre Arbeit so zusammenstellen können, dass die einzelnen Arbeitseinheiten klein sind und die Benutzeroberfläche nicht so stark beeinflusst. Auf diese Weise können Sie den Dispatcher direkt verwenden. Auf der WPF-Threading-Seite finden Sie ein gutes Beispiel: https://msdn.Microsoft.com/en-us/library/ms741870%28v=vs.100%29.aspx

4
Anderson Imes

Wie der Name schon sagt, wird es im Hintergrund ausgeführt, sodass Sie es nicht mit dem Dispatcher instanziieren müssen. Wenn Sie möchten, dass dieser Code in einem WP7 ausgeführt wird, erhält BeginInvoke den Hintergrundparameter nicht.

Meine Empfehlung ist, den BackgroundWorker zu erstellen als:

BackgroundWorker worker = new BackgroundWorker;

Erstellen Sie dann die Handler für die Ereignisse:

worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork +=new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.ProgressChanged +=new ProgressChangedEventHandler(worker_ProgressChanged);

Und zum Schluss rufen Sie an:

bkwkPlayingLoop.RunWorkerAsync();

Es ist eine große Versuchung, den Dispatcher innerhalb von DoWork zu verwenden, stattdessen worker.ReportProgress () aufzurufen und die Benutzeroberfläche von dort aus zu bearbeiten. Andernfalls werden Sie beim Auslösen von Beendigungsereignissen auf einige Inkonsistenzen stoßen.

2
Julio Bailon

Aufgaben sind einfacher zu bedienen als Hintergrundarbeiter, erledigen mehr Aufgaben, haben weniger Probleme und wurden so gut wie geschaffen, sodass Hintergrundarbeiter nicht mehr verwendet werden mussten ...

0
MattE