[C#] Verwenden des BackgroundWorker

Drucken
( 24 Votes )
Hauptkategorie: Programmieren Kategorie: C#
Erstellt am 14.12.2011 Zuletzt aktualisiert am 28.08.2012 Geschrieben von Jonny132
Bei zeitaufwändigen Aufgaben (zB das einlesen einer sehr großen Textdatei) kann es schon vorkommen, dass die Form sozusagen einfriert. Das passiert weil alles in einem Thread abläuft und so die Form keine Chance hat auf Benutzereingaben zu reagieren bzw. sich neu zu zeichnen. Am einfachsten kann man so ein Verhalten simulieren in dem man ein Thread.Sleep(10000) einbaut (vorher using System.Threading einbauen). Das lässt die Form für 10 Sekunden einfrieren.

Um dieses einfrieren zu verhindern gibt es den Threading - Namespace. Hier gibt es haufenweise Möglichkeiten weitere Threads zu erstellen (Multithreading). Um dieses komplizierte Thema Threading etwas zu vereinfachen, hat .NET den BackgroundWorker eingeführt. Diesen möchte ich euch nun anhand eines Beispieles zeigen.

Erstellen Sie ein Windows Forms Projekt und fügen Sie dieser einen Button (btnStartEnd) und eine Progressbar (progressBar1) hinzu und wechseln Sie zur Codeansicht.
Hier fügen wir zuerst das Using hinzu.
using System.Threading;

Den erwähnten Backgroundworker erstellen wir global in der Klasse Form1

Globale BackgroundWorker Variable

BackgroundWorker worker;

 Form Load Ereigniss

 private void Form1_Load(object sender, EventArgs e)
 {
   worker = new BackgroundWorker();
   worker.WorkerReportsProgress = true;
   worker.WorkerSupportsCancellation = true;
 
   worker.DoWork += new DoWorkEventHandler(worker_DoWork);
   worker.ProgressChanged += 
               new ProgressChangedEventHandler(worker_ProgressChanged);
   worker.RunWorkerCompleted += 
              new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
 }


Im Form Load Ereignis erstellen wir zuerst eine neue Instanz des Backgroundworkers und setzen die Eigenschaften WorkerReportsProgress und WorkerSupportsCancellation auf true.
WorkerReportsProgress: Benötigen wir um während der BackgroundWorker beschäftigt ist Statusse an die Form zu übergeben um den Fortschritt anzuzeigen.
WorkerSupportsCancellation: Damit wir die verarbeitung im BackgroundWorker jederzeit abbrechen können.

Dann registrieren wir noch die benötigten Events des Backgroundworkers um auf jedes Einzutreffende Ereignis reagieren zu können.
 

DoWork Event des BackgroundWorkers

void worker_DoWork(object sender, DoWorkEventArgs e)
 {
   int percentFinished = (int)e.Argument;
   while (!worker.CancellationPending && percentFinished < 100)
   {
     percentFinished++;
     worker.ReportProgress(percentFinished);
     System.Threading.Thread.Sleep(50);
   }
   e.Result = percentFinished;
 }
In das DoWork - Event kommt nun der Zeitaufwändige Codeteil bei dem die Form im normalFall einfrieren würde.
Zu Beachten ist, dass in diesem Event NICHT auf UserControls zugegriffen werden darf, da wir nicht Threadübergreiffend Werte übergeben dürfen (ausser man verwendet Invoke).
Im Event weisen wir zuerst einen Prozentwert zu, falls der Worker gestoppt wurde und nun die Aufgabe weitergemacht werden soll.
Dann erstellen wir eine While Schleife die solange läuft bis 100% erreicht wurden bzw der Thread mittels CancelAsynch() gestoppt wurde. In der While Schleife inkrementieren wir den % Status und sagen dem Backgroundworker, dass sich was geändert hat indem wir die ReportProgress - Methode aufrufen. Damit der Thread nich zu schnell fertig ist und zur besseren veranschaulichung habe ich noch ein Thread.Sleep von 50 Millisekunden eingebaut. Zu Schluss des DoWork Events geben wir noch den aktuellen Prozentwert als Result (Ergebnis) mit.
 

ProgressChanged Ereigniss des Backgroundworker

 void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
 {
   progressBar1.Value = e.ProgressPercentage;
 }

Das ProgressChanged - Event wird immer aufgerufen, wenn die ReportProgress() - Methode des BackgroundWorkers aufgerufen wird (wird oben im DoWork - Event verwendet).
Alles was wir hier machen ist, den Wert der ProgressBar zu aktualisieren. Den Aktuellen Prozentwert bekommen wir aus den ProgressChangedEventArgs.

RunWorkerCompleted Event des BackgroundWorker

 void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 {
   MessageBox.Show("Asynchroner Thread kam bis zum Wert:
 "+e.Result.ToString());
   btnStartEnd.Text = "Starten";
 }
Wie der EventName vermuten lässt, wird das RunWorkerCompleted - Event aufgerufen sobald der Backgroundworker mit seiner Aufgabe fertig ist. Dabei macht er keine Unterscheidung, ob die verarbeitung nun bis zum Ende gelaufen ist oder der Code durch setzen des CancelAsync() abgebrochen wurde.
In einer MessageBox zeigen wir nun bis zu wieviel % der BackgroundWorker gekommen ist und geben dem Button wieder den Text "Starten" um den Backgroundworker entweder Neu zu Starten oder Weiterlaufen zu lassen.

Nun fehlt nur noch das Handling des Buttons.

Click Event des Button

 private void btnStartEnd_Click(object sender, EventArgs e)
 {
   if (worker.IsBusy)
   {
     worker.CancelAsync();
     btnStartEnd.Text = "Starten";
   }
   else
   {
      if (progressBar1.Value == progressBar1.Maximum)
      {
        progressBar1.Value = progressBar1.Minimum;
      }
      worker.RunWorkerAsync(progressBar1.Value);
      btnStartEnd.Text = "Stoppen";
   }
 }


Wenn der BackgroundWorker gerade beschäftigt ist, soll er gestoppt werden mittels CancelAsync(). Dadurch wird intern das Flag CancellationPending auf True gesetzt. Zusätzlich stellen wir den ButtonText wieder auf Starten falls wir den BackgroundWorker weiterlaufen lassen wollen.
Ist der BackgroundWorker nicht beschäftigt, so überprüfen ob die Progressbar zum Zeitpunkt schon den Maximumwert hat. Trifft dies zu, setzen wir ihn wieder auf den MinimumWert.
Unabhängig dessen, wird der BackgroundWorker mittels RundWorkerAsync() gestartet. Als Parameter übergeben wir den aktuellen ProgressBar Wert. Den Button Text setzten wir noch auf Stoppen, falls der Benutzer den BackgroundWorker abbrechen will.

Das wars. Hier ein Screenshot wie es aussehen könnte:
BackgroundWorker

  • Michael

    schrieb am 2014-06-01 15:19:31

    Danke für den Code! Ist eigentlich sehr simpel und effektiv und hat nicht viele Fehlerquellen! Top

    Auf Kommentar antworten

  • Klaus Sucker

    schrieb am 2013-10-23 11:28:38

    Spitzen Tutorial !!
    Danke

    Auf Kommentar antworten

  • Andre

    schrieb am 2013-06-23 08:12:30

    Absolut Top Erklärung!!

    Herzlichen Dank

    Tipp: Plural von Status ist Status (Aussprache des 'u' ist lang) und nicht Statusse oder Stati, wie viele meinen ;-)

    Auf Kommentar antworten

  • andreas sfhneider

    schrieb am 2013-04-30 08:31:56

    Mir fehlt hier eine sehr wichtige info, namlich dass in worker_RunWorkerCompleted in den ui thread geschrieben werden darf, resp. Man sich in diesem befindet, ist das so? Wie geht das intern?
    Gruss

    Auf Kommentar antworten

    • Jonny132

      schrieb am 2013-04-30 09:32:27

      Hallo Andreas,
      deine Annahme ist korrekt... Im RunWorkerCompleted-Event darf wieder auf die Controls bzw. den anderen Thread zugegriffen werden.

      Im Prinzip darfst du nur nicht im DoWork() auf den anderen Thread / die GUI zugreiffen, da dieser in einem extrigen Thread ausgeführt wird.

      Falls du weitere Fragen hast, steht dir jederzeit das Forum zur Verfügung :)

      sg

      Auf Kommentar antworten

  • Jack

    schrieb am 2013-04-26 21:31:02

    Es funktioniert bei mir nicht

    Auf Kommentar antworten

    • Jonny132

      schrieb am 2013-04-29 11:22:16

      Hallo Jack,

      Der Code ist relativ simple und enthält nicht wirklich viele Fehlerquellen.
      Mit der Aussage "Funktioniert nicht" kann ich dir leider nicht helfen.

      Das Forum ist nicht nur zur Zirde da, wenn du Probleme hast ;)

      Auf Kommentar antworten

Veröffentlichen Sie ihre Kommentare ...