Die ultimative VB.NET Background Worker Anleitung in 2024
Inhaltsverzeichnis
- 1 Intro
- 2 ++ Update 20.12.2022++
- 3 Multithreading mit Backgroundworker in .NET
- 4 Backgroundworker Beispiel – Der Code
- 5 Zukunftsvisionen und Aktuelles
- 6 Der vollständige Code
- 7 Downloads
Intro
In diesem ausführlichen Backgroundworker Beispiel, zeige ich dir alles, was Du über Ihn wissen musst: Die grundsätzliche Funktionalität, Tipps & Tricks und mehr.
Ebenso relevant wie die Funktionsweise des Backgroundworkers, ist natürlich auch, ob es im .NET Framework mittlerweile modernere Alternativen gibt.
So wie die Zeit, bleibt natürlich auch das NET Framework nicht stehen und alte Technologien, bzw. Teile des Frameworks werden durch neuere und ggf. bessere – oder einfachere – Methoden erweitert.
++ Update 20.12.2022++
Ich habe zusätzlich noch den ganzen Code für C# hinzugefügt. So haben auch die C#-Kollegen die Möglichkeit, alles schnell und via „Copy & Paste“ zu bekommen. (Ich selbst entwickle ja auch meist nur noch in C#, außer ein Kunde möchte es anders..). Grundsätzlich gilt hier natürlich, dass beides ja im Endeffekt .NET-Sprachen sind und sich daher die rein logischen Unterschiede absolut in Grenzen halten. Ebenso habe ich noch eine Restrukturierung des Beitrags geplant, aber das muss erstmal noch warten.
Multithreading mit Backgroundworker in .NET
Wofür bzw. was ist ein BGW?
Zuerst einmal wirst du dich vermutlich bei deinem ersten Kontakt mit einem Backgroundworker – kurz BGW – fragen, wozu du Ihn brauchst.
Der Backgroundworker ist wie der Name schon vermuten lässt dazu da, Aufgaben im Hintergrund abzuarbeiten.
Stelle dir den BGW wie einen Handwerker vor, den du mit etwas beauftragst, was etwas mehr Zeit in Anspruch nimmt.
Solange er z. B. den Rasen mäht, oder die Spülmaschine repariert, hast du hingegen Zeit für andere Sachen.
Praxisbeispiele
Beispiele aus der Praxis wären eventuell:
- Zugriffe auf die Festplatte für die Auflistung von Verzeichnissen und Dateien
- oder etwa das scannen von Ports einer gewissen IP-Adresse.
Wenn der Handwerker – ergo der BGW – seine Arbeit erledigt hat, kann er es dir mitteilen.
Alternativ kann er auch zwischendurch einen kleinen Status darüber liefern, wie weit er mit seiner Arbeit ist.
Während der Handwerker seine Arbeit verrichtet, könntest du Ihm natürlich auch ein Glas Wasser anbieten und ihn pausieren lassen.
Kein Genuss für Anfänger
Zugegebenermaßen ist die Verwendung des Backgroundworkers vor Allem für Anfänger nicht gerade ein Genuss, geschweige denn einfach. So habe ich es auf jeden Fall bei mehreren meiner Schüler, bzw. aus dem Netz und früher auch am eigenen Leib erlebt.
Mit ausreichend Erfahrung kann sich ein fortgeschrittener Programmierer natürlich die jeweiligen Puzzleteile für einen Backgroundworker zusammenfügen.
Andersherum schlagen sich besonders Anfänger mühsam mit den hier genannten Fragen herum. Dabei geht es hauptsächlich um Themen wie die Übergabe von Parametern an den Backgroundworker. Ein weiteres Beispiel wäre die Anzeige eines Status in z. B. einer ProgressBar.
Gängige Fragen
In meinem Backgroundworker-Beispiel findest du Code und Erklräungen, Welche die Antworten auf folgende Fragen liefern:
- Wofür brauche ich einen Backgroundworker?
- Wie pausiere ich einen Backgroundworker (pause)?
- Wie breche ich einen Backgroundworker ab (cancel)?
- Wie kann ein Backgroundworker einen Fortschritt wiedergeben (report)?
- Wie kann ich dem Backgroundworker einen Parameter übergeben?
- Wie übergebe ich dem Backgroundworker mehrere Parameter?
- Wie zeige ich mit Hilfe des Backgroundworkers den Status in einer ProgressBar – Thread und GUI sicher – an?
- Gibt es modernere Alternativen zum Backgroundworker?
Die Antworten auf diese Fragen versuche ich durch den Code zu veranschaulichen und letztendlich zu erklären.
Backgroundworker Beispiel – Der Code
Backgroundworker deklarieren
Definiere den Backgroundworker innerhalb der Form, also innerhalb der Klasse. Achte darauf, dass du ihn außerhalb der Methoden also als Member der Klasse positionierst.
Private WithEvents Worker As BackgroundWorker
private BackgroundWorker Worker;
Private oder Dim
Zuerst verwenden wir das „Private“-Schlüsselwort. Es stellt schon den ersten Unterschied zu vielerlei online auffindbaren Code dar.
„Private“ ist ein Zugriffsmodifizierer, Welcher neben Anderen und im Gegensatz zu dem häufig verwendeten „Dim“ auf Klasssenebene verwendet wird.
„Dim“ hat zwar den selben Effekt, also es würde auch in diesem Fall den Worker als Private kennzeichnen. Leider ist es aber meiner Meinung nach unsauber. Es dient nur zur Deklaration bzw. Initialisierung einer lokalen Variable in z. B. einem Konstruktor oder einer „normalen“ Methode.
WithEvents
Das „WithEvents“-Schlüsselwort gibt an, dass ein oder mehrere auf Klassen- oder Modulebene deklarierte Elemente, Ereignisse hervorrufen können.
Im Falle des Backgroundworkers sind das unter Anderem das „ProgressChanged„- und das „RunWorkerCompleted„-Ereignis.
Worker
Das ist einfach nur der Name, mit dessen Hilfe wir den jeweiligen Backgroundworker – auf Klassenebene – im Code ansprechen und verwenden können.
As BackgroundWorker
Die „As“-Klausel bzw. das „As“-Schlüsselwort dient in der Deklaration als „Zuweisungsoperator“ für einen Datentyp. Praktisch übersetze heißt das dann einfach: „Du bist vom Typ X„. In unserem Fall: „Du bist vom Datentyp BackgroundWorker„.
Das ist natürlich besonders durch das „WithEvents“-Schlüsselwort wichtig, damit „unser Code auch weiß“ von wem, welche Ereignisse ausgelöst werden können.
New
Häufig findet man im online recherchierten Code so etwas wie:
Private WithEvents Worker As New BackgroundWorker
private BackgroundWorker Worker = new BackgroundWorker();
Davon halte ich ebenfalls nichts, da es sich auch hier unsauber anfühlt. Instanziierungen sollten im dafür vorgesehenen Schritt des – ich nenne es mal – Lebenszyklus durchgeführt werden.
Das wäre meines Erachtens nach im Konstruktor der jeweiligen Klasse. Dort werden – ggf. auch durch weitere Methoden (Subs) – die verschiedenen Objekte auf ihren Einsatz vorbereitet.
Backgroundworker vorbereiten
Im Konstruktor der Form, Welchen wir durch das Tippen von s-u-b Leerzeichen n-e-w und Enter generieren können:
Sub New() ' Dieser Aufruf ist für den Designer erforderlich. InitializeComponent() ' Fügen Sie Initialisierungen nach dem InitializeComponent()-Aufruf hinzu. End Sub
public Form1() { // Dieser Aufruf ist für den Designer erforderlich. InitializeComponent(); // Fügen Sie Initialisierungen nach dem InitializeComponent()-Aufruf hinzu. }
fügen wir nun eine Methode namens „InitializeBackgroundWorker“ hinzu. Anschließend erstellen wir Diese, oder lassen Diese via Visual Studios Hilfsfunktionen generieren.
Hilfsmethode für die Initialisierung im Konstruktor
Sub New() InitializeComponent() InitializeBackgroundWorker() InitializeProgressBar() End Sub
public Form1() { InitializeComponent(); InitializeBackgroundWorker(); InitializeProgressBar(); }
In dieser Funktion bereiten wir – wie der Name schon verraten lässt – den Backgroundworker auf seine sprichwörtliche Arbeit vor.
Die Methode ist bewusst als Private gekennzeichnet, da ja nur die Form, bzw. dessen Konstruktor Sie verwenden soll. Von außen braucht keiner (also keine andere Klasse) darauf zugreifen.
Private Sub InitializeBackgroundWorker() Worker = New BackgroundWorker() Worker.WorkerReportsProgress = True Worker.WorkerSupportsCancellation = True End Sub
private void InitializeBackgroundWorker() { Worker = new BackgroundWorker(); Worker.WorkerReportsProgress = true; Worker.WorkerSupportsCancellation = true; }
Die Instanziierung
Wir initialisieren die Backgroundworker-Variable mit einer neuen Instanz und konfigurieren anschließend einige Eigenschaften.
WorkerReportsProgress
Wer der englischen Sprache einigermaßen mächtig ist – und das sollte man als Softwareentwickler durchaus sein – wird schnell erörtern können, was die Eigenschaft WorkerReportsProgress macht.
Die Eigenschaft legt fest, ob der BGW Fortschrittsaktualisierungen melden kann (Standard: False).
WorkerSupportsCancellation
Auch hier lässt sich relativ einfach verstehen, was die Eigenschaft WorkerSupportsCancellation konfigurieren soll.
Sie legt fest, ob der BackgroundWorker asynchrone Abbrüche unterstützt (Standard: False).
Hilfsmethode für die ProgressBar
Anbei die kleine Hilfsmethode, Welche uns bei der Vorbereitung der ProgressBar unterstützen soll.
Private Sub InitializeProgressBar() pgbStatus.Minimum = 0 pgbStatus.Maximum = 100 pgbStatus.Value = 0 End Sub
private void InitializeProgressBar() { pgbStatus.Minimum = 0; pgbStatus.Maximum = 100; pgbStatus.Value = 0; }
Den Backgroundworker starten
Bevor wir uns weiter mit den Details der einzelnen „Handler“ beschäftigen, widmen wir uns noch dem einfacheren Szenario: Den BGW zu starten, also ihn mit seiner Arbeit loslegen zu lassen.
Dafür ziehen wir uns im Designer der Form einfach einen Button auf die Form und benennen Diesen natürlich passend, z. B. „btnStart“.
Anschließend klicken wir im Designer doppelt auf den Button, um das ich nenne es mal „Standard-Ereignis“ des Buttons programmieren zu können.
Bei einem Button handelt es sich natürlich um das „Klick-Ereignis„, Welches dann mit einem passenden Ereignis-Handler (Methode) verknüpft wird. Dies geschieht durch das „Handles“-Schlüsselwort.
Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click If Worker.IsBusy Then Return End If Worker.RunWorkerAsync() End Sub
private void btnStart_Click(object sender, EventArgs e) { if (Worker.IsBusy) return; Worker.RunWorkerAsync(); }
Im obigen Code haben wir nun eine Methode namens btnStart_Click, also ganz konform der Visual Studio Bezeichnungsregeln a la <Controlname_Ereignisname>.
Bei der Signatur des Eventhandlers handelt es sich – Aufgrund nicht nötiger Zusatzinformationen zum Klick – eher um ein „billiges Modell„.
Es ist lediglich der „sender“ enthalten, der natürlich das Control widerspiegelt, Welches dieses Ereignis ausgelöst hat und die „Standard-Ereignisargument-Klasse“ namens Eventargs.
Die Handles-Klausel sagt dann letztendlich aus, dass diese Sub doch bitte dann aufgerufen werden möchte, wenn der btnStart sein Klick-Ereignis signalisiert.
So viel zur Definition, bzw. Deklaration der Handler-Methode, nun zum inneren Teil. Hier werden wir einen kleinen Check inkl. das letztendliche Starten des Backgroundworkers programmieren.
Doppelt hält nicht immer besser
Der erste Teil der Methode wird durch den folgenden kleinen Check dargestellt:
If Worker.IsBusy Then Return End If
if (Worker.IsBusy) return;
Hier prüfen wir, ob der Backgroundworker schon beschäftigt ist, er also bereits arbeitet.
So wie einem der Handwerker im echten Leben vermutlich einen Vogel zeigen würde, wenn wir Ihm beim Dachdecken noch sagen würden, dass er den Rasen mähen soll, macht es auch der Backgroundworker ähnlich.
Falls wir versuchen sollten den Backgroundworker während der Ausführung seiner Arbeit erneut zu starten, wirft er – so nennt man es „leider“ – den folgenden Fehler – eine InvalidOperationException:
Der Worker als Teil vom Ganzen
Auch wenn es rein von der Logik her betrachtet vermutlich mehr Sinn machen würde, eine Public Property a la „Drucke Rechnungen“ vom Typ Boolean hinzuzufügen, prüfen wir die Ausführung des Backgroundworkers in diesem simplen Beispiel direkt.
Die öffentliche Eigenschaft wäre meiner Meinung nach hier deshalb eher angebracht, weil der Status des Backgroundworkers ja nicht der ausschlaggebende Punkt für eine größere Aufgabe darstellt. Sie stellt eher einen kleinen Teil des Großen und Ganzen dar.
Wenn wir zum Beispiel die Aufgabe hätten die gesammelten Rechnungen der letzten Tage zu drucken, dann machen wir die Ausführung des Backgroundworkers – Welcher ja zum Beispiel nur das Sammeln und Aufarbeiten der Rechnungen übernehmen könnte – eher vom übergeordneten Flag namens (z. B.) IsPrintingBills abhängig.
Ein Bild zur Veranschaulichung
Die Aufbereitung der Rechnungen, also der Arbeitsabschnitt vom BGW, ist ja letztendlich nur ein kleiner Teil davon. Es könnten ja zum Beispiel vorher und nachher noch Datenbankzugriffe uvm. stattfinden.
Im Bild stellt der BGW praktisch nur den Sub-Prozess bzw. Methode 3 des Gesamten dar.
Jeweilige andere Methoden könnten daher natürlich auch wiederum eigene, spezielle Exceptions – also Fehler – „werfen“ und somit separat abgefangen und verarbeitet werden.
Der Start selbst
Den Backgroundworker final starten, können wir mit dessen Methode namens RunWorkerAsync:
Worker.RunWorkerAsync()
Worker.RunWorkerAsync();
Mit dem Backgroundworker arbeiten
Nachdem wir nun die allgemeinen Vorbereitungen getroffen haben und Nebeninformationen besprochen haben, kommen wir nun final zu den restlichen Aspekten.
Der DoWork Handler
Als erstes brauchen wir eine Methode, die ausgeführt wird, wenn der Worker sein „DoWork-Ereignis“ signalisiert. Dort programmieren wir das hinein, was letztendlich im Hintergrund – also asynchron – ausgeführt wird.
Die grobe Signatur inkl. Handler-Verknüpfung und leerer Sub sieht so aus:
Private Sub Worker_DoWork(sender As Object, e As DoWorkEventArgs) Handles Worker.DoWork ' what to do? End Sub
// bei C# die Verknüpfung von Ereignis zu Handler nicht vergessen! private void Worker_DoWork(object sender, DoWorkEventArgs e) { // what to do? }
Die erste Frage die man sich nun stellen könnte wäre: „Okay, und wie übergebe ich dem Backgroundworker jetzt einen Parameter?“
Backgroundworker einen Parameter übergeben
Das ist natürlich etwas, was sich jeder Anfänger in der Verwendung des BGWs einmal fragen wird und im Endeffekt ist es – wenn man weiß, wo man suchen muss – gar nicht so schwer.
Wer sich die Methode RunWorkerAsync einmal genauer angeschaut hat, wird feststellen, dass man in einer weiteren Überladung der Methode auch etwas als Parameter mitgeben kann.
Würden wir analog zu unserem Beispiel nur die Rechnungen von heute abarbeiten wollen, könnte man es zum Beispiel so lösen:
Worker.RunWorkerAsync("today")
Worker.RunWorkerAsync("today");
In dem DoWork-Handler können wir dann via e.Argument an den übergebenen Parameter kommen. Da es sich bei dem Argument um ein Object handelt, müssen wir es noch in den korrekten Datentyp casten:
Private Sub Worker_DoWork(sender As Object, e As DoWorkEventArgs) Handles Worker.DoWork Dim argument = Convert.ToString(e.Argument) ' do something with the passed argument End Sub
private void Worker_DoWork(object sender, DoWorkEventArgs e) { var argument = Convert.ToString(e.Argument); // do something with the passed argument }
Backgroundworker mehrere Parameter übergeben
Nun könnte man sich als nächstes die Frage stellen: „Okay und was mache ich nun, wenn ich dem Backgroundworker mehrere Parameter übergeben möchte? So geht es ja nur mit einem Parameter!“
Wie sagt man so schön oft und gerne in der Programmierung: „It depends“ – „Es kommt darauf an„. Es gibt sicherlich gewisse Fallbeispiele, in denen es reichen würde z. B. eine ein Datum und eine Kunden-ID wie folgt zu übergeben:
Worker.RunWorkerAsync("today,5")
Worker.RunWorkerAsync("today,5");
So könnte man diese Parameter dann einfach in dem DoWork-Handler abgreifen:
Private Sub Worker_DoWork(sender As Object, e As DoWorkEventArgs) Handles Worker.DoWork Dim arguments = Convert.ToString(e.Argument).Split(",") Dim date = arguments(0) Dim customerId = Convert.ToInt32(arguments(1)) ' do something End Sub
private void Worker_DoWork(object sender, DoWorkEventArgs e) { var arguments = Convert.ToString(e.Argument).Split(","); var date = arguments(0); var customerId = Convert.ToInt32(arguments(1)); // do something }
Allerdings ist diese Methode weder sauber, noch sehr zukunftssicher, ändert sich mal was an der Schreibweise, kommen neue Werte dazu, oder ändert sich die Reihenfolge, könnte man schon wieder ein Problem haben.
Auch wenn ein anderer Programmierer, der zum Beispiel in der gleichen Firma wie Ihr arbeitet, den Code falsch aufruft:
Worker.RunWorkerAsync("5,today")
Worker.RunWorkerAsync("5,today");
sprich die Reihenfolge vertauscht, oder das falsche Trennzeichen verwendet, gibt es direkt wieder Probleme.
Richtig mehrere Parameter übergeben
Wie man es stattdessen „richtig“ machen könnte sieht man in folgendem Beispiel:
Public Class BillPrintArgs Public [Date] As String Public CustomerId As Integer Sub New([date] As String, customerId As Integer) Me.Date = [date] Me.CustomerId = customerId End Sub End Class
public class BillPrintArgs { public string Date { get; set; } public int CustomerId { get; set; } public BillPrintArgss(string date, int customerId) { Date = date; CustomerId = customerId; } }
Ich habe hier eine extra dafür gebaute Klasse erstellt, die die jeweiligen Parameter dann als Properties/Eigenschaften anbietet. Der Konstruktor der Klasse bietet mir auch sofort die Möglichkeit passende Werte mitzuliefern.
Da ich nicht von der bisherigen Bezeichnung abweichen wollte, habe ich nun einen kleinen Workaround – die eckigen Klammern – nutzen müssen. „Date“ wird ansonsten als reservierter Begriff erkannt und kann nicht verwendet werden.
Nun könnte der Aufruf der RunWorkerAsync-Methode wie folgt aussehen:
Dim billPrintArgs = New BillPrintArgs("today", 5) Worker.RunWorkerAsync(billPrintArgs)
var billPrintArgs = new BillPrintArgs("today", 5); Worker.RunWorkerAsync(billPrintArgs);
In diesem Fall würde man dann so typensicher an das jeweilige Argument kommen:
Private Sub Worker_DoWork(sender As Object, e As DoWorkEventArgs) Handles Worker.DoWork Dim billPrintArgs = CType(e.Argument, BillPrintArgs) ' do something with the passed argument End Sub
private void Worker_DoWork(object sender, DoWorkEventArgs e) { var billPrintArgs = (BillPrintArgs) e.Argument; // do something with the passed argument }
Achtung beim Zugriff auf die GUI
Da der Backgroundworker im Hintergrund arbeitet, sprich in einem anderen Thread, kann man nicht wie gewohnt auf die GUI zugreifen.
Threadübergreifender Vorgang
Versucht man dies z.B. wie folgt dennoch:
Private Sub Worker_DoWork(sender As Object, e As DoWorkEventArgs) Handles Worker.DoWork lblStatus.Text = "Test" End Sub
private void Worker_DoWork(object sender, DoWorkEventArgs e) { lblStatus.Text = "Test"; }
bekommt man einen Fehler:
Backgroundworker abbrechen / canceln
Hat der Backgroundworker erstmal seine Arbeit begonnen, können wir Diesem auch ein Abbruchsignal senden, falls der Benutzer unserer Anwendung zum Beispiel das Programm schließt.
Eventuell entscheidet sich der Anwender auch aus anderen Gründen dazu den Prozess – zum Beispiel durch einen Button – abzubrechen.
Achtung – Fehler
Voraussetzung für das Abbruchsignal an den Backgroundworker ist, die jeweilige Eigenschaft dafür vorher korrekt konfiguriert zu haben, sprich WorkerSupportsCancellation, ansonsten funktioniert das Ganze nicht.
Senden wir dem Backgroundworker über die Methode CancelAsync trotzdem ein Abbruchsignal und haben dabei vergessen, die WorkerSupportsCancellation Eigenschaft auf „True“ zu setzen, kommt folgender Fehler:
Der Abbruch
Wir fügen als nächstes einen neuen Button und einen Handler hinzu:
Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click Worker.CancelAsync() End Sub
private void btnCancel_Click(object sender, EventArgs e) { Worker.CancelAsync(); }
Der Button ruft dann die CancelAsync-Methode auf. Zunächst bewirkt dies nichts! Denn wir müssen darauf explizit in dem DoWork-Handler reagieren:
Private Sub Worker_DoWork(sender As Object, e As DoWorkEventArgs) Handles Worker.DoWork Dim billPrintArgs = CType(e.Argument, BillPrintArgs) For i = 0 To 1000000 If Worker.CancellationPending Then e.Cancel = True Return End If ' Do something.. Thread.Sleep(200) Next End Sub
private void Worker_DoWork(object sender, DoWorkEventArgs e) { var billPrintArgs = (BillPrintArgs) e.Argument; for (var i = 0; i <= 1000000; i++) { if (Worker.CancellationPending) { e.Cancel = true; Return; } // Do something.. Thread.Sleep(200); } }
Auf CancellationPending achten
Wenn der Backgroundworker nun ein Abbruchsignal bekommen hat, würde die Eigenschaft CancellationPending auf True stehen und dadurch das e.Cancel Flag auf True gesetzt werden.
Anschließend wird die Methode noch mit Return verlassen und die For-Schleife somit abgebrochen/beendet.
Auf Abbruch reagieren
Im RunWorkerCompleted-Handler kann man mit Hilfe der RunWorkerCompletedEventArgs überprüfen, ob es sich um einen Abbruch handelt.
Dazu schaut man sich den Wert in der EventArgs–Eigenschaft namens Cancelled an (e.Cancelled).
Backgroundworker Statusmeldungen
Damit der Backgroundworker Statusmeldungen von sich geben kann, müssen wir Ihn mit der Eigenschaft WorkerReportsProgress auf True konfigurieren.
Achtung Fehler
Falls wir – wie oben beim Abbruch – auch hier vergessen sollten, die Eigenschaft auf True zu stellen, werden wir auch hier mit einem Fehler abgespeist:
Ist alles korrekt konfiguriert, können wir nun in dem DoWork-Handler die ReportProgress-Methode des Backgroundworkers verwenden.
Statusmeldungen abgeben
Dabei können wir auf 2 Varianten zurückgreifen:
- ReportProgress(<FortschrittInProzent>)
- ReportProgress(<FortschrittInProzent>, <EigenesObjekt>)
Wenn man also stumpf den Prozentwert weiterleiten möchte, reicht wohl die erste Überladung der Methode aus.
Möchte man jedoch komplexere Daten weitergeben, empfiehlt es sich auch hier, eine eigene Klasse zu schreiben
Public Class BillPrintProgressArgs Public Property Information As String ' more Properties.. Sub New(information As String) Me.Information = information End Sub End Class
public class BillPrintProgressArgs { public string Information { get; set; } // more Properties.. public BillPrintProgressArgs(string information) { Information = information; } }
und die zweite Überladung zu verwenden:
Private Sub Worker_DoWork(sender As Object, e As DoWorkEventArgs) Handles Worker.DoWork Dim billPrintArgs = CType(e.Argument, BillPrintArgs) ' do something with args Dim maxIterations = 1000 For i = 0 To maxIterations If Worker.CancellationPending Then e.Cancel = True Return End If Thread.Sleep(20) Dim percentage = Convert.ToInt32((i / maxIterations) * 100) Worker.ReportProgress(percentage , new BillPrintProgressArgs("My information: " & i)) Next End Sub
private void Worker_DoWork(object sender, DoWorkEventArgs e) { var billPrintArgs = (BillPrintArgs) e.Argument; // do something with args var maxIterations = 1000; for (var i = 0; i <= maxIterations; i++) { if (Worker.CancellationPending) { e.Cancel = true; return; } Thread.Sleep(20); var percentage = Convert.ToInt32((i / maxIterations) * 100); Worker.ReportProgress(percentage , new BillPrintProgressArgs("My information: " + i)); } }
Statusmeldungen empfangen und verarbeiten
Nun können wir in dem dafür vorgesehenen Handler die jeweiligen Informationen verarbeiten. Wir können den Text mit der Information über die i-Variable aktualisieren. Ebenfalls können wir die Progressbar aktualisieren.
Private Sub Worker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles Worker.ProgressChanged Dim billPrintProgressArgs = CType(e.UserState, BillPrintProgressArgs) lblStatus.Text = "Status: " & billPrintProgressArgs.Information pgbStatus.Value = e.ProgressPercentage End Sub
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { var billPrintProgressArgs = (BillPrintProgressArgs) e.UserState; lblStatus.Text = "Status: " + billPrintProgressArgs.Information; pgbStatus.Value = e.ProgressPercentage; }
Zukunftsvisionen und Aktuelles
Leider hat der Backgroundworker – wenn nicht erzwungen durch eine gewisse NET Framework Version – meiner Meinung nach mehr als ausgedient.
Es gibt wesentlich bessere und vor allem modernere Alternativen wie z. B. das Async/Await Pattern.
Mit den einzelnen Subs, bzw. Handlern ist der Backgroundworker nicht gerade ein Schmaus für den Verwender.
Viel schöner wäre es doch, wenn man Ihn wie in folgendem Pseudocode dargestellt verwenden könnte:
Sub Hauptmethode() BackgroundWorker1.MachDeineArbeit() ' fortfahren, ohne weitere Subs, etc. End Sub
private void Hauptmethode() { BackgroundWorker1.MachDeineArbeit(); // fortfahren, ohne weitere Subs, etc. }
Dieses Pattern ist mit der neueren Variante der asynchronen Programmierung namens Async/Await möglich.
Darüber kannst Du in meinem passenden Beitrag über VB NET Async Await mehr erfahren.
Final sollten also besonders neue Projekte mit diesem neueren Muster programmiert und umgesetzt werden.
Der vollständige Code
Wer es eilig hat, kann sich den vollständigen Code direkt hier herauskopieren.
Imports System.ComponentModel Imports System.Threading Public Class Form1 Private WithEvents Worker As BackgroundWorker Sub New() InitializeComponent() InitializeBackgroundWorker() InitializeProgressBar() End Sub Private Sub InitializeBackgroundWorker() Worker = New BackgroundWorker() Worker.WorkerReportsProgress = True Worker.WorkerSupportsCancellation = True End Sub Private Sub InitializeProgressBar() pgbStatus.Minimum = 0 pgbStatus.Maximum = 100 pgbStatus.Value = 0 End Sub Public Class BillPrintArgs Public [Date] As String Public CustomerId As Integer Sub New([date] As String, customerId As Integer) Me.Date = [date] Me.CustomerId = customerId End Sub End Class Public Class BillPrintProgressArgs Public Property Information As String ' more Properties.. Sub New(information As String) Me.Information = information End Sub End Class Private Sub Worker_DoWork(sender As Object, e As DoWorkEventArgs) Handles Worker.DoWork Dim billPrintArgs = CType(e.Argument, BillPrintArgs) ' do something with args Dim maxIterations = 1000 For i = 1 To maxIterations If Worker.CancellationPending Then e.Cancel = True Return End If Thread.Sleep(20) Dim percentage = Convert.ToInt32((i / maxIterations) * 100) Worker.ReportProgress(percentage, New BillPrintProgressArgs("Information: " & i.ToString())) Next End Sub Private Sub Worker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles Worker.ProgressChanged Dim billPrintProgressArgs = CType(e.UserState, BillPrintProgressArgs) lblStatus.Text = "Status: " & billPrintProgressArgs.Information pgbStatus.Value = e.ProgressPercentage End Sub Private Sub Worker_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles Worker.RunWorkerCompleted lblStatus.Text = "Worker completed – Cancelled: " & e.Cancelled.ToString() End Sub Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click If Worker.IsBusy Then Return End If Dim billPrintArgs = New BillPrintArgs(DateTime.Now.ToString("dd.MM.YYYY"), 5) lblStatus.Text = "Gestartet" pgbStatus.Value = 0 Worker.RunWorkerAsync(billPrintArgs) End Sub Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click Worker.CancelAsync() End Sub End Class
using System.ComponentModel; using System.Threading; public class Form1 { private BackgroundWorker Worker; public Form1() { InitializeComponent(); InitializeBackgroundWorker(); InitializeProgressBar(); } private void InitializeBackgroundWorker() { Worker = new BackgroundWorker(); Worker.WorkerReportsProgress = true; Worker.WorkerSupportsCancellation = true; } private void InitializeProgressBar() { pgbStatus.Minimum = 0; pgbStatus.Maximum = 100; pgbStatus.Value = 0; } public class BillPrintArgs { public string Date { get; set; } public int CustomerId { get; set; } public BillPrintArgs(string date, int customerId) { Date = date; CustomerId = customerId; } } public class BillPrintProgressArgs { public string Information { get; set; } // more Properties.. public BillPrintProgressArgs(string information) { Information = information; } } private void Worker_DoWork(object sender, DoWorkEventArgs e) var billPrintArgs = (BillPrintArgs) e.Argument; // do something with args var maxIterations = 1000; for (var i = 1; i <= maxIterations; i++) if (Worker.CancellationPending) { e.Cancel = true; return; } Thread.Sleep(20); var percentage = Convert.ToInt32((i / maxIterations) * 100); Worker.ReportProgress(percentage, New BillPrintProgressArgs("Information: " + i.ToString())); } } private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { var billPrintProgressArgs = (BillPrintProgressArgs) e.UserState; lblStatus.Text = "Status: " + billPrintProgressArgs.Information; pgbStatus.Value = e.ProgressPercentage; } private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { lblStatus.Text = "Worker completed – Cancelled: " + e.Cancelled.ToString(); } private void btnStart_Click(object sender, EventArgs e) { if (Worker.IsBusy) return; var billPrintArgs = new BillPrintArgs(DateTime.Now.ToString("dd.MM.YYYY"), 5); lblStatus.Text = "Gestartet"; pgbStatus.Value = 0; Worker.RunWorkerAsync(billPrintArgs); } private void btnCancel_Click(object sender, EventArgs e) { Worker.CancelAsync(); } }