Async / warte gegen BackgroundWorker

162

In den letzten Tagen habe ich die neuen Funktionen von .net 4.5 und c # 5 getestet.

Ich mag die neuen Async / Wait-Funktionen. Früher hatte ich BackgroundWorker verwendet , um längere Prozesse im Hintergrund mit reaktionsschneller Benutzeroberfläche zu verarbeiten.

Meine Frage ist: Nach diesen netten neuen Funktionen, wann sollte ich async / await verwenden und wann ein BackgroundWorker ? Welches sind die gemeinsamen Szenarien für beide?

Tom
quelle
Beide sind gut, aber wenn Sie mit älterem Code arbeiten, der nicht auf eine spätere .net-Version migriert wurde; BackgroundWorker funktioniert auf beiden.
dcarl661

Antworten:

74

async / await soll Konstrukte wie das ersetzen BackgroundWorker. Sie können es sicherlich verwenden, wenn Sie möchten, aber Sie sollten in der Lage sein, async / await zusammen mit einigen anderen TPL-Tools zu verwenden, um alles zu handhaben, was da draußen ist.

Da beide funktionieren, kommt es auf die persönlichen Vorlieben an, welche Sie wann verwenden. Was geht für Sie schneller ? Was ist für Sie leichter zu verstehen?

Servieren
quelle
17
Vielen Dank. Für mich scheint Async / Warten viel klarer und "natürlicher" zu sein. BakcgoundWorker macht den Code meiner Meinung nach "laut".
Tom
12
@ Tom Nun, deshalb hat Microsoft viel Zeit und Mühe in die Implementierung investiert . Wenn es nicht besser wäre, hätten sie sich nicht darum
gekümmert
5
Ja. Das neue Warten lässt den alten BackgroundWorker völlig minderwertig und veraltet erscheinen. Der Unterschied ist so dramatisch.
usr
16
Ich habe einen ziemlich guten Überblick in meinem Blog , in dem verschiedene Ansätze für Hintergrundaufgaben verglichen werden. Beachten Sie, dass async/ awaitauch asynchrone Programmierung ohne Thread-Pool-Threads ermöglicht.
Stephen Cleary
8
Diese Antwort wurde abgelehnt, sie ist irreführend. Async / await ersetzt NICHT den Hintergrundarbeiter.
Quango
206

Dies ist wahrscheinlich TL; DR für viele, aber ich denke, das Vergleichen awaitmit BackgroundWorkerist wie das Vergleichen von Äpfeln und Orangen, und meine Gedanken dazu folgen:

BackgroundWorkersoll eine einzelne Aufgabe modellieren, die Sie im Hintergrund in einem Thread-Pool-Thread ausführen möchten. async/ awaitist eine Syntax für das asynchrone Warten auf asynchrone Operationen. Diese Operationen können einen Thread-Pool-Thread oder sogar einen anderen Thread verwenden oder nicht . Sie sind also Äpfel und Orangen.

Sie können beispielsweise Folgendes tun mit await:

using (WebResponse response = await webReq.GetResponseAsync())
{
    using (Stream responseStream = response.GetResponseStream())
    {
        int bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length);
    }
}

Aber Sie würden wahrscheinlich nie modellieren, dass Sie in einem Hintergrund-Worker wahrscheinlich so etwas in .NET 4.0 (vorher await) tun würden :

webReq.BeginGetResponse(ar =>
{
    WebResponse response = webReq.EndGetResponse(ar);
    Stream responseStream = response.GetResponseStream();
    responseStream.BeginRead(buffer, 0, buffer.Length, ar2 =>
    {
        int bytesRead = responseStream.EndRead(ar2);
        responseStream.Dispose();
        ((IDisposable) response).Dispose();
    }, null);
}, null);

Beachten Sie die Disjunktheit der Entsorgung im Vergleich zwischen den beiden Syntaxen und wie Sie usingohne async/ nicht verwenden können await.

Aber so etwas würden Sie nicht machen BackgroundWorker. BackgroundWorkerdient normalerweise zum Modellieren einer einzelnen Operation mit langer Laufzeit, die die Reaktionsfähigkeit der Benutzeroberfläche nicht beeinträchtigen soll. Beispielsweise:

worker.DoWork += (sender, e) =>
                    {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                        ++i;
                    };
worker.RunWorkerCompleted += (sender, eventArgs) =>
                                {
                                    // TODO: do something on the UI thread, like
                                    // update status or display "result"
                                };
worker.RunWorkerAsync();

Es gibt wirklich nichts, mit dem Sie async / await verwenden können. Es BackgroundWorkererstellt den Thread für Sie.

Jetzt können Sie stattdessen TPL verwenden:

var synchronizationContext = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
                      {
                        int i = 0;
                        // simulate lengthy operation
                        Stopwatch sw = Stopwatch.StartNew();
                        while (sw.Elapsed.TotalSeconds < 1)
                            ++i;
                      }).ContinueWith(t=>
                                      {
                                        // TODO: do something on the UI thread, like
                                        // update status or display "result"
                                      }, synchronizationContext);

In diesem Fall TaskSchedulererstellt der Thread den Thread für Sie (unter der Annahme der Standardeinstellung TaskScheduler) und kann awaitwie folgt verwendet werden:

await Task.Factory.StartNew(() =>
                  {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                        ++i;
                  });
// TODO: do something on the UI thread, like
// update status or display "result"

Meiner Meinung nach ist ein wichtiger Vergleich, ob Sie Fortschritte melden oder nicht. Zum Beispiel könnten Sie Folgendes haben BackgroundWorker like:

BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.ProgressChanged += (sender, eventArgs) =>
                            {
                            // TODO: something with progress, like update progress bar

                            };
worker.DoWork += (sender, e) =>
                 {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                    {
                        if ((sw.Elapsed.TotalMilliseconds%100) == 0)
                            ((BackgroundWorker)sender).ReportProgress((int) (1000 / sw.ElapsedMilliseconds));
                        ++i;
                    }
                 };
worker.RunWorkerCompleted += (sender, eventArgs) =>
                                {
                                    // do something on the UI thread, like
                                    // update status or display "result"
                                };
worker.RunWorkerAsync();

Aber Sie würden sich nicht mit etwas davon befassen, weil Sie die Hintergrund-Worker-Komponente auf die Entwurfsoberfläche eines Formulars ziehen und dort ablegen würden - etwas, das Sie mit async/ awaitund Task... nicht tun können, dh Sie haben gewonnen. ' t Erstellen Sie das Objekt manuell, legen Sie die Eigenschaften und die Ereignishandler fest. Sie würden nur in dem Körper der füllen DoWork, RunWorkerCompletedund ProgressChangedEvent - Handler.

Wenn Sie das in async / await "konvertieren" würden, würden Sie etwas tun wie:

     IProgress<int> progress = new Progress<int>();

     progress.ProgressChanged += ( s, e ) =>
        {
           // TODO: do something with e.ProgressPercentage
           // like update progress bar
        };

     await Task.Factory.StartNew(() =>
                  {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                    {
                        if ((sw.Elapsed.TotalMilliseconds%100) == 0)
                        {
                            progress.Report((int) (1000 / sw.ElapsedMilliseconds))
                        }
                        ++i;
                    }
                  });
// TODO: do something on the UI thread, like
// update status or display "result"

Ohne die Möglichkeit, eine Komponente auf eine Designer-Oberfläche zu ziehen, muss der Leser wirklich entscheiden, welche "besser" ist. Aber das ist für mich der Vergleich zwischen awaitund BackgroundWorkernicht, ob Sie auf eingebaute Methoden wie warten können Stream.ReadAsync. Wenn Sie beispielsweise BackgroundWorkerwie vorgesehen verwenden, kann es schwierig sein, die Konvertierung in die Verwendung zu konvertieren await.

Andere Gedanken: http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html

Peter Ritchie
quelle
2
Ein Fehler, den ich bei async / await glaube, besteht darin, dass Sie möglicherweise mehrere asynchrone Aufgaben gleichzeitig starten möchten. Das Warten soll warten, bis jede Aufgabe abgeschlossen ist, bevor die nächste gestartet wird. Wenn Sie das Schlüsselwort await weglassen, wird die Methode synchron ausgeführt, was nicht Ihren Wünschen entspricht. Ich glaube nicht, dass async / await ein Problem wie "Starten Sie diese 5 Aufgaben und rufen Sie mich zurück, wenn jede Aufgabe in keiner bestimmten Reihenfolge ausgeführt wird" lösen kann.
Trevor Elliott
4
@ Mozhe. Nicht wahr, du kannst es tun var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;. Welches würde zwei parallele Operationen warten. Warten ist viel besser für asynchrone, aber sequentielle Aufgaben, IMO ...
Peter Ritchie
2
@Moozhe ja, wenn man es so macht, bleibt eine bestimmte Reihenfolge erhalten - wie ich schon sagte. Dies ist der Hauptpunkt des Wartens, um die Asynchronität in sequentiell aussehendem Code zu erhalten. Sie können natürlich await Task.WhenAny(t1, t2)auch etwas tun, wenn eine der beiden Aufgaben zuerst abgeschlossen wurde. Sie möchten wahrscheinlich eine Schleife, um sicherzustellen, dass auch die andere Aufgabe abgeschlossen ist. Normalerweise möchten Sie wissen, wann eine bestimmte Aufgabe abgeschlossen ist, was dazu führt, dass Sie sequentielle awaits schreiben .
Peter Ritchie
2
War es nicht .NET 4.0 TPL, das asynchrone Muster von APM, EAP und BackgroundWorker überflüssig machte? Immer noch verwirrt darüber
Gennady Vanin Геннадий Ванин
5
Ehrlichkeit, BackgroundWorker war nie gut für IO-gebundene Operationen.
Peter Ritchie
21

Dies ist eine gute Einführung: http://msdn.microsoft.com/en-us/library/hh191443.aspx Der Abschnitt Threads ist genau das, wonach Sie suchen:

Asynchrone Methoden sollen nicht blockierende Vorgänge sein. Ein Warte-Ausdruck in einer asynchronen Methode blockiert den aktuellen Thread nicht, während die erwartete Aufgabe ausgeführt wird. Stattdessen meldet der Ausdruck den Rest der Methode als Fortsetzung und gibt die Kontrolle an den Aufrufer der asynchronen Methode zurück.

Die Schlüsselwörter async und await führen nicht dazu, dass zusätzliche Threads erstellt werden. Asynchrone Methoden erfordern kein Multithreading, da eine asynchrone Methode nicht in einem eigenen Thread ausgeführt wird. Die Methode wird im aktuellen Synchronisationskontext ausgeführt und verwendet die Zeit im Thread nur, wenn die Methode aktiv ist. Sie können Task.Run verwenden, um CPU-gebundene Arbeit in einen Hintergrundthread zu verschieben. Ein Hintergrundthread hilft jedoch nicht bei einem Prozess, der nur darauf wartet, dass Ergebnisse verfügbar werden.

Der asynchrone Ansatz zur asynchronen Programmierung ist in fast allen Fällen bestehenden Ansätzen vorzuziehen. Insbesondere ist dieser Ansatz für E / A-gebundene Vorgänge besser als BackgroundWorker, da der Code einfacher ist und Sie sich nicht vor Rennbedingungen schützen müssen. In Kombination mit Task.Run ist die asynchrone Programmierung für CPU-gebundene Vorgänge besser als BackgroundWorker, da die asynchrone Programmierung die Koordinierungsdetails für die Ausführung Ihres Codes von der Arbeit trennt, die Task.Run in den Threadpool überträgt.

TommyN
quelle
"Für IO-gebundene Operationen, weil der Code einfacher ist und Sie sich nicht vor Rennbedingungen schützen müssen" Welche Rennbedingungen können auftreten, können Sie ein Beispiel geben?
eran otzap
8

BackgroundWorker wird in .NET 4.5 ausdrücklich als veraltet gekennzeichnet:

Der MSDN-Artikel "Asynchrone Programmierung mit Async und Await (C # und Visual Basic)" lautet :

Der asynchrone Ansatz zur asynchronen Programmierung ist in fast allen Fällen bestehenden Ansätzen vorzuziehen . Insbesondere ist dieser Ansatz für E / A-gebundene Vorgänge besser als BackgroundWorker, da der Code einfacher ist und Sie sich nicht vor Rennbedingungen schützen müssen. In Kombination mit Task.Run ist die asynchrone Programmierung für CPU-gebundene Vorgänge besser als BackgroundWorker, da die asynchrone Programmierung die Koordinierungsdetails für die Ausführung Ihres Codes von der Arbeit trennt , die Task.Run in den Threadpool überträgt

AKTUALISIEREN

  • als Antwort auf den Kommentar von @ eran-otzap :
    "Für IO-gebundene Operationen, weil der Code einfacher ist und Sie sich nicht vor Rennbedingungen schützen müssen" Welche Rennbedingungen können auftreten, können Sie ein Beispiel geben? ""

Diese Frage hätte als separater Beitrag gestellt werden sollen.

Wikipedia hat eine gute Erklärung für Rennbedingungen. Der notwendige Teil davon ist Multithreading und aus demselben MSDN-Artikel Asynchrone Programmierung mit Async und Await (C # und Visual Basic) :

Asynchrone Methoden sollen nicht blockierende Vorgänge sein. Ein Warte-Ausdruck in einer asynchronen Methode blockiert den aktuellen Thread nicht, während die erwartete Aufgabe ausgeführt wird. Stattdessen meldet der Ausdruck den Rest der Methode als Fortsetzung und gibt die Kontrolle an den Aufrufer der asynchronen Methode zurück.

Die Schlüsselwörter async und await führen nicht dazu, dass zusätzliche Threads erstellt werden. Asynchrone Methoden erfordern kein Multithreading, da eine asynchrone Methode nicht in einem eigenen Thread ausgeführt wird. Die Methode wird im aktuellen Synchronisationskontext ausgeführt und verwendet die Zeit im Thread nur, wenn die Methode aktiv ist. Sie können Task.Run verwenden, um CPU-gebundene Arbeit in einen Hintergrundthread zu verschieben. Ein Hintergrundthread hilft jedoch nicht bei einem Prozess, der nur darauf wartet, dass Ergebnisse verfügbar werden.

Der asynchrone Ansatz zur asynchronen Programmierung ist in fast allen Fällen bestehenden Ansätzen vorzuziehen. Insbesondere ist dieser Ansatz für E / A-gebundene Vorgänge besser als BackgroundWorker, da der Code einfacher ist und Sie sich nicht vor Rennbedingungen schützen müssen. In Kombination mit Task.Run ist die asynchrone Programmierung für CPU-gebundene Vorgänge besser als BackgroundWorker, da die asynchrone Programmierung die Koordinierungsdetails für die Ausführung Ihres Codes von der Arbeit trennt, die Task.Run in den Threadpool überträgt

Das heißt: "Die Schlüsselwörter asynchron und warten führen nicht dazu, dass zusätzliche Threads erstellt werden."

Soweit ich mich an meine eigenen Versuche erinnern kann, als ich diesen Artikel vor einem Jahr studierte, könnten Sie, wenn Sie ein Codebeispiel aus demselben Artikel ausgeführt und damit gespielt haben, auf die Situation stoßen, dass es sich um nicht asynchrone Versionen handelt (Sie könnten versuchen, sie zu konvertieren es für dich) auf unbestimmte Zeit blockieren!

Für konkrete Beispiele können Sie auch diese Site durchsuchen. Hier einige Beispiele:

Gennady Vanin Геннадий Ванин
quelle
30
BackgrondWorker wird in .NET 4.5 nicht explizit als veraltet gekennzeichnet. Der MSDN-Artikel besagt lediglich, dass E / A-gebundene Operationen mit asynchronen Methoden besser sind. Die Verwendung von BackgroundWorker bedeutet nicht, dass Sie keine asynchronen Methoden verwenden können.
Peter Ritchie
@ PeterRitchie, ich habe meine Antwort korrigiert. "Bestehende Ansätze sind veraltet" ist für mich gleichbedeutend mit "Der asynchrone Ansatz zur asynchronen Programmierung ist in fast jedem Fall bestehenden Ansätzen vorzuziehen"
Gennady Vanin Геннадий Ванин
7
Ich habe Probleme mit dieser MSDN-Seite. Zum einen führen Sie mit BGW nicht mehr "Koordination" durch als mit Task. Und ja, BGW war nie dazu gedacht, E / A-Operationen direkt auszuführen - es gab immer einen besseren Weg, E / A-Operationen durchzuführen als in BGW. Die andere Antwort zeigt, dass BGW nicht komplexer zu verwenden ist als Task. Und wenn Sie BGW richtig einsetzen, gibt es keine Rennbedingungen.
Peter Ritchie
"Für IO-gebundene Operationen, weil der Code einfacher ist und Sie sich nicht vor Rennbedingungen schützen müssen" Welche Rennbedingungen können auftreten, können Sie ein Beispiel geben?
eran otzap
11
Diese Antwort ist falsch. Asynchrone Programmierung kann zu leicht Deadlocks in nicht trivialen Programmen auslösen. Im Vergleich dazu ist BackgroundWorker einfach und absolut solide.
ZunTzu