BackgroundWorker vs Hintergrund Thread

166

Ich habe eine stilistische Frage zur Auswahl der Hintergrund-Thread-Implementierung, die ich in einer Windows Form-App verwenden sollte. Derzeit habe ich BackgroundWorkerein Formular mit einer (while(true))Endlosschleife. In dieser Schleife WaitHandle.WaitAnydöse ich den Thread so lange, bis etwas Interessantes passiert. Eine der Ereignisbehandlungen, auf die ich warte, ist ein " StopThread" Ereignis, damit ich aus der Schleife ausbrechen kann. Dieses Ereignis wird signalisiert, wenn es von mir überschrieben wird Form.Dispose().

Ich habe irgendwo gelesen, BackgroundWorkerwas wirklich für Operationen gedacht ist, mit denen Sie die Benutzeroberfläche nicht verknüpfen möchten und die ein endliches Ende haben - wie das Herunterladen einer Datei oder das Verarbeiten einer Folge von Elementen. In diesem Fall ist das "Ende" unbekannt und nur bei geschlossenem Fenster. Wäre es daher angemessener, einen Hintergrund-Thread anstelle BackgroundWorkerdieses Zwecks zu verwenden?

Freddy Smith
quelle

Antworten:

88

Nach meinem Verständnis Ihrer Frage verwenden Sie einen BackgroundWorkerals Standard-Thread.

Der Grund, warum dies BackgroundWorkerfür Dinge empfohlen wird, bei denen Sie den UI-Thread nicht binden möchten, liegt darin, dass bei der Entwicklung von Win Forms einige nette Ereignisse angezeigt werden.

Ereignisse RunWorkerCompletedsignalisieren gerne , wann der Thread abgeschlossen hat, was zu tun ist, und das ProgressChangedEreignis zum Aktualisieren der GUI über den Fortschritt des Threads.

Wenn Sie diese also nicht nutzen, sehe ich keinen Schaden darin, einen Standard-Thread für das zu verwenden, was Sie tun müssen.

ParmesanCodice
quelle
Ein weiteres Problem, bei dem ich mir nicht sicher bin, ist, dass ich versuche, das Formular zu entsorgen, auf dem der Hintergrundarbeiter ausgeführt wird. Ich signalisiere das Shutdownevent (ManualResetEvent) und irgendwann danach wird DoWork ordnungsgemäß beendet. Sollte ich das Formular einfach weiterleiten und entsorgen lassen, obwohl das Beenden des DoWork möglicherweise etwas länger dauert, oder gibt es eine Möglichkeit (und ist es besser), einen Thread zu erstellen? Treten Sie dem Hintergrund-Worker bei, bis er wirklich beendet wird, und lassen Sie dann das Entsorgen der Form weiter?
Freddy Smith
Ich denke, BackgroundWorker.IsBusy ist das, wonach Sie dort suchen.
ParmesanCodice
1
Verwenden Sie nur CancelAsync(und testen Sie, CancellationPendingob Ihr Thread in kurzen Intervallen abfragt. Wenn Sie stattdessen eine Ausnahme auslösen möchten, verwenden Sie eine System.Threading.Thread.Abort(), die eine Ausnahme innerhalb des Thread-Blocks selbst auslöst, und wählen Sie das richtige Modell für die
Brett Ryan
369

Einige meiner Gedanken ...

  1. Verwenden Sie BackgroundWorker, wenn Sie eine einzelne Aufgabe haben, die im Hintergrund ausgeführt wird und mit der Benutzeroberfläche interagieren muss. Das Marshalling von Daten und Methodenaufrufen an den UI-Thread wird automatisch über das ereignisbasierte Modell erledigt. Vermeiden Sie BackgroundWorker, wenn ...
    • Ihre Assembly hat keine oder interagiert nicht direkt mit der Benutzeroberfläche.
    • Der Thread muss ein Vordergrund-Thread sein, oder
    • Sie müssen die Thread-Priorität bearbeiten.
  2. Verwenden Sie einen ThreadPool- Thread, wenn Effizienz gewünscht wird. Mit dem ThreadPool können Sie den Aufwand vermeiden, der mit dem Erstellen, Starten und Stoppen von Threads verbunden ist. Vermeiden Sie die Verwendung des ThreadPool, wenn ...
    • Die Aufgabe wird für die Lebensdauer Ihrer Anwendung ausgeführt.
    • Sie benötigen den Thread, um ein Vordergrund-Thread zu sein.
    • Sie müssen die Thread-Priorität manipulieren, oder
    • Der Thread muss eine feste Identität haben (Abbrechen, Anhalten, Erkennen).
  3. Verwenden Sie die Thread- Klasse für Aufgaben mit langer Laufzeit und wenn Sie Funktionen benötigen, die von einem formalen Threading-Modell angeboten werden, z. B. Wählen zwischen Vordergrund- und Hintergrund-Threads, Optimieren der Thread-Priorität, fein abgestimmte Kontrolle über die Thread-Ausführung usw.
Matt Davis
quelle
10
Der Hintergrund-Worker befindet sich in der System.dll-Assembly und im System.ComponentModel-Namespace. Es besteht keine Abhängigkeit von Winforms.
Kugel
17
Das ist richtig, soll aber den BackgroundWorkerThread-Fortschritt einer interessierten Partei melden, was normalerweise eine Benutzeroberfläche umfasst. Die MSDN-Dokumentation für die Klasse macht dies sehr deutlich. Wenn Sie lediglich eine Aufgabe im Hintergrund ausführen möchten, verwenden Sie lieber einen ThreadPoolThread.
Matt Davis
5
In Bezug auf Ihren Standpunkt zur System.Windows.FormsVersammlung; BackgroundWorkerist auch für WPF-Apps nützlich und diese Apps haben möglicherweise keinen Verweis auf WinForms.
GiddyUpHorsey
12

Ziemlich genau das, was Matt Davis gesagt hat, mit den folgenden zusätzlichen Punkten:

Für mich ist das Hauptunterscheidungsmerkmal BackgroundWorkerdas automatische Marshalling des abgeschlossenen Events über das SynchronizationContext. In einem UI-Kontext bedeutet dies, dass das abgeschlossene Ereignis im UI-Thread ausgelöst wird und somit zum Aktualisieren der UI verwendet werden kann. Dies ist ein wesentliches Unterscheidungsmerkmal, wenn Sie das BackgroundWorkerin einem UI-Kontext verwenden.

Aufgaben über die Ausführung ThreadPoolkann nicht einfach gelöscht werden (dies beinhaltet ThreadPool. QueueUserWorkItemUnd Delegierten ausführen asynchron). Während dies den Aufwand für das Hochfahren von Threads vermeidet, verwenden Sie entweder einen BackgroundWorkeroder (wahrscheinlicher außerhalb der Benutzeroberfläche) das Hochfahren eines Threads und behalten Sie einen Verweis darauf, damit Sie ihn aufrufen können Abort().

piers7
quelle
1
Nur ... hoffentlich ist die Anwendung auf eine saubere Methode zum Stoppen einer Thread-Aufgabe ausgelegt (Abbruch ist im Allgemeinen nicht der
11

Außerdem binden Sie einen Threadpool-Thread für die Lebensdauer des Hintergrundarbeiters, was von Belang sein kann, da es nur eine begrenzte Anzahl von ihnen gibt. Ich würde sagen, wenn Sie den Thread nur einmal für Ihre App erstellen (und keine der Funktionen des Hintergrundarbeiters verwenden), verwenden Sie einen Thread anstelle eines Hintergrundarbeiters / Threadpool-Threads.

Matt
quelle
1
Ich denke, das ist ein guter Punkt. Die Nachricht, die ich daraus entnehme, lautet: Verwenden Sie einen Hintergrund-Worker, wenn Sie während der Lebensdauer des Formulars "vorübergehend" einen Hintergrund-Thread benötigen. Wenn Sie jedoch einen Hintergrund-Thread für die gesamte Lebensdauer des Formulars benötigen (dies können Minuten, Stunden, Tage ...) dann verwenden Sie einen Thread anstelle von BackgroundWorker, um den Zweck des ThreadPool
Freddy Smith
In Bezug auf: "... was besorgniserregend sein kann, da es nur eine begrenzte Anzahl von ihnen gibt", meinen Sie damit, dass andere Apps auf dem Betriebssystem sie möglicherweise benötigen und aus demselben 'Pool' freigeben?
Dan W
8

Wissen Sie, manchmal ist es einfacher, mit einem BackgroundWorker zu arbeiten, unabhängig davon, ob Sie Windows Forms, WPF oder eine andere Technologie verwenden. Das Schöne an diesen Jungs ist, dass Sie Threading erhalten, ohne sich zu viele Gedanken darüber machen zu müssen, wo Ihr Thread ausgeführt wird, was sich hervorragend für einfache Aufgaben eignet.

Bevor Sie eine BackgroundWorkerÜberlegung zuerst verwenden, wenn Sie einen Thread abbrechen möchten (App schließen, Benutzer abbrechen), müssen Sie entscheiden, ob Ihr Thread auf Abbrüche prüfen soll oder ob er bei der Ausführung selbst ausgeführt werden soll.

BackgroundWorker.CancelAsync()wird eingestellt CancellationPending, trueaber es wird nichts mehr getan. Es liegt dann in der Verantwortung des Threads, dies kontinuierlich zu überprüfen. Beachten Sie auch, dass bei diesem Ansatz möglicherweise eine Race-Bedingung auftritt, bei der Ihr Benutzer den Vorgang abgebrochen hat, der Thread jedoch vor dem Testen abgeschlossen wurde CancellationPending.

Thread.Abort() Auf der anderen Seite wird eine Ausnahme innerhalb der Thread-Ausführung ausgelöst, die das Abbrechen dieses Threads erzwingt. Sie müssen jedoch vorsichtig sein, was gefährlich sein könnte, wenn diese Ausnahme plötzlich während der Ausführung ausgelöst wird.

Das Einfädeln muss unabhängig von der Aufgabe sehr sorgfältig überlegt werden, um weiterzulesen:

Parallele Programmierung in den Best Practices für verwaltetes Threading in .NET Framework

Brett Ryan
quelle
5

Ich wusste, wie man Threads verwendet, bevor ich .NET kannte, daher war es etwas gewöhnungsbedürftig, als ich anfing, BackgroundWorkers zu verwenden. Matt Davis hat den Unterschied mit großer Exzellenz zusammengefasst, aber ich möchte hinzufügen, dass es schwieriger ist, genau zu verstehen, was der Code tut, und dies kann das Debuggen erschweren. Es ist einfacher, über das Erstellen und Herunterfahren von Threads nachzudenken, IMO, als darüber nachzudenken, einem Pool von Threads Arbeit zu geben.

Ich kann die Beiträge anderer Leute immer noch nicht kommentieren, also verzeihen Sie meine momentane Lahmheit, wenn Sie eine Antwort verwenden, um piers7 anzusprechen

Verwenden Sie Thread.Abort();stattdessen nicht, signalisieren Sie ein Ereignis und gestalten Sie Ihren Thread so, dass er ordnungsgemäß endet, wenn er signalisiert wird. Thread.Abort()Löst ThreadAbortExceptionan einem beliebigen Punkt in der Ausführung des Threads ein aus, das alle Arten von unglücklichen Dingen wie verwaiste Monitore, beschädigten gemeinsam genutzten Status usw. ausführen kann.
http://msdn.microsoft.com/en-us/library/system.threading.thread.abort.aspx

ajs410
quelle
2

Wenn es nicht kaputt ist - reparieren Sie es, bis es ... nur ein Scherz :)

Aber im Ernst, BackgroundWorker ist wahrscheinlich sehr ähnlich zu dem, was Sie bereits haben. Hätten Sie von Anfang an damit begonnen, hätten Sie vielleicht etwas Zeit gespart - aber an diesem Punkt sehe ich keine Notwendigkeit. Wenn etwas nicht funktioniert oder Sie denken, dass Ihr aktueller Code schwer zu verstehen ist, würde ich mich an das halten, was Sie haben.

Gandalf
quelle
2

Der grundlegende Unterschied besteht, wie Sie bereits sagten, darin, GUI-Ereignisse aus dem zu generieren BackgroundWorker. Wenn der Thread die Anzeige nicht aktualisieren oder Ereignisse für den Haupt-GUI-Thread generieren muss, kann es sich um einen einfachen Thread handeln.

David R Tribble
quelle
2

Ich möchte auf ein Verhalten der BackgroundWorker-Klasse hinweisen, das noch nicht erwähnt wurde. Sie können einen normalen Thread so einstellen, dass er im Hintergrund ausgeführt wird, indem Sie die Thread.IsBackground-Eigenschaft festlegen.

Hintergrund-Threads sind identisch mit Vordergrund-Threads, außer dass Hintergrund-Threads nicht verhindern, dass ein Prozess beendet wird. [ 1 ]

Sie können dieses Verhalten testen, indem Sie die folgende Methode im Konstruktor Ihres Formularfensters aufrufen.

void TestBackgroundThread()
{
    var thread = new Thread((ThreadStart)delegate()
    {
        long count = 0;
        while (true)
        {
            count++;
            Debug.WriteLine("Thread loop count: " + count);
        }
    });

    // Choose one option:
    thread.IsBackground = true; // <--- This will make the thread run in background
    thread.IsBackground = false; // <--- This will delay program termination

    thread.Start();
}

Wenn die IsBackground-Eigenschaft auf true festgelegt ist und Sie das Fenster schließen, wird Ihre Anwendung normal beendet.

Wenn die IsBackground-Eigenschaft jedoch (standardmäßig) auf false festgelegt ist und Sie das Fenster schließen, wird nur das Fenster ausgeblendet, der Prozess wird jedoch weiterhin ausgeführt.

Die BackgroundWorker-Klasse verwendet einen Thread, der im Hintergrund ausgeführt wird.

Doomjunky
quelle
1

Ein Hintergrund-Worker ist eine Klasse, die in einem separaten Thread arbeitet, aber zusätzliche Funktionen bietet, die Sie mit einem einfachen Thread nicht erhalten (z. B. Behandlung von Aufgabenfortschrittsberichten).

Wenn Sie die zusätzlichen Funktionen eines Hintergrundarbeiters nicht benötigen - und anscheinend auch nicht -, ist ein Thread besser geeignet.

Cesar
quelle
-1

Was mich verwirrt, ist, dass Sie mit dem Visual Studio Designer nur BackgroundWorker und Timer verwenden können, die mit dem Serviceprojekt nicht funktionieren.

Sie erhalten ordentliche Drag & Drop-Steuerelemente für Ihren Dienst, aber ... versuchen Sie nicht einmal, ihn bereitzustellen. Wird nicht funktionieren.

Dienste: Verwenden Sie nur System.Timers.Timer System.Windows.Forms.Timer funktioniert nicht, obwohl es in der Toolbox verfügbar ist

Dienste: BackgroundWorkers funktionieren nicht, wenn es als Dienst ausgeführt wird. Verwenden Sie stattdessen System.Threading.ThreadPools oder Async-Aufrufe

scottweeden
quelle