Die ultimative VB.NET Background Worker Anleitung in 2024

BackgroundWorker Beispiel in VB.NET – Der Handwerker im Hintergrund
BackgroundWorker Beispiel in VB.NET – Der Handwerker im Hintergrund

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

Häufige Backgroundworker Fragen
Häufige Backgroundworker 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

BackgroundWorker starten
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:

Backgroundworker mehrfach gestartet – InvalidOperationException
Backgroundworker mehrfach gestartet – 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

Backgroundworker Beispiel mit Sub-Prozessen bzw. Methoden
Backgroundworker Beispiel mit Sub-Prozessen bzw. Methoden

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
}
Stop den BGW so besser nicht verwenden!
Stop den Backgroundworker so besser nicht verwenden!

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:

InvalidOperationException – Threadübergreifender Vorgang
Backgroundworker InvalidOperationException – Threadübergreifender Vorgang

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:

Backgroundworker InvalidOperationException – Abbruch wird nicht unterstützt
Backgroundworker InvalidOperationException – Abbruch wird nicht unterstützt

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 EventArgsEigenschaft 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:

Backgroundworker InvalidOperationException – Statusmeldungen werden nicht unterstützt
Backgroundworker InvalidOperationException – Statusmeldungen werden nicht unterstützt

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();
    }

}

Downloads

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert