Multithreading in C#, Teil 3: Foreground Thread vs. Background Thread vs. BackgroundWorker

In diesem Teil der Artikelserie möchte ich Ihnen die Unterschiede zwischen einem Foreground Thread und einem Background Thread aufzeigen und danach auf die Hilfsklasse BackgoundWorker eingehen. Des weiteren Stelle ich Ihnen ein Template zur Implementierung eines BackgroundWorker Threads bereit.

 
Foreground Thread vs. Background Thread

Ein Thread kann als Foreground Thread oder als Background Thread gestartet werden. Standardmässig ist ein Thread beim Erstellen ein Foreground Thread. Ein Background Thread kann erzeugt werden indem die IsBackground Eigenschaft auf true gesetzt wird

Der Hauptunterschied zwischen den beiden Formen liegt in ihrer Auswirkung auf die Lebensdauer der Anwendung. Eine Anwendung wird erst geschlossen wenn alle Foreground Threads beendet wurden. Beim Schliessen der Anwendung werden automatisch alle aktiven Background Threads beendet, unabhängig von deren aktuellem Ausführungsstatus. Der Hauptthread der Applikation ist immer ein Foreground Thread.

Der nachfolgende Quellcode zeigt einen Foreground Thread.

    1 class Program

    2 {

    3     static void Main(string[] args)

    4     {

    5         Thread worker = new Thread(DoWork);

    6         worker.Start();

    7     }

    8 

    9     static void DoWork()

   10     {

   11         Thread.Sleep(TimeSpan.FromSeconds(5));

   12     }

   13 }

 
Wenn diese Applikation gestartet wird, bleibt sie fünf Sekunden lang aktiv und wird dann geschlossen. Was geschieht aber wenn der Thread als Background Thread gestartet wird? Der nachfolgende Quellcode zeigt das gleiche Beispielprogramm aber diesmal wird die IsBackground Eigenschaft des Threads gesetzt (Zeile 6).

    1 class Program

    2 {

    3     static void Main(string[] args)

    4     {

    5         Thread worker = new Thread(DoWork);

    6         worker.IsBackground = true;

    7         worker.Start();

    8     }

    9 

   10     static void DoWork()

   11     {

   12         Thread.Sleep(TimeSpan.FromSeconds(5));

   13     }

   14 }

 
Wenn diese Applikation gestartet wird, wird sie sofort beendet. Der Background Thread welcher eigentlich fünf Sekunden aktiv ist, wirkt sich nicht auf die Lebensdauer der Anwendung aus.

Sie stellen sich bestimmt schon die Frage was mit diesem Thread geschieht. Wird er sauber beendet oder einfach abrupt unterbrochen? Dies ist eine sehr wichtige Frage besonders wenn innerhalb des Threads Ressourcen genutzt werden welche am Ende wieder freigegeben werden müssen. Das nächste Kapitel widmet sich daher diesem Thema.

 
Background Threads bei Shutdown beenden
Beim Schliessen einer Anwendung werden Background Threads automatisch beendet. Um verwendete Ressourcen freizugeben, könnte der Thread beispielsweise einen finally Block enthalten. Die nachfolgende Testanwendung soll klären ab der finally Block auch beim Shutdown einer Anwendung aufgerufen wird.

    1 class Program

    2 {

    3     static void Main(string[] args)

    4     {

    5         Thread worker = new Thread(DoWork);

    6         worker.IsBackground = true;

    7         worker.Start();

    8     }

    9 

   10     static void DoWork()

   11     {

   12         try

   13         {

   14             Thread.Sleep(TimeSpan.FromSeconds(5));

   15         }

   16         finally

   17         {

   18             Console.WriteLine("cleanup executed");

   19         }

   20     }

   21 }

 
Wie bereits gezeigt, wird die Applikation sofort Beendet und der Background Thread kann nicht fertig prozessiert werden. Der finally Block wird dabei nicht ausgeführt. Der Thread wird also abrupt beendet. Das heisst, Aufräumarbeiten wie zum Beispiel Ressourcenfreigaben in finally oder using Anweisungen werden nicht ausgeführt. Beim Schliessen einer Applikation sollten Sie daher die Background Threads sauber beenden. Dazu sollte dem Thread ein Abbruch signalisiert werden und danach mittels Join Anweisung auf das Ende des Threads gewartet werden. Am besten nutzen Sie in der Join Anweisung ein Timeout um im Fehlerfall die Applikation nicht zu blockieren. Der nachfolgende Quellcode zeigt das vorhergehende Beispiel erweitert um die Funktionalität zum Beenden des Background Threads.

    1 class Program

    2 {

    3     static void Main(string[] args)

    4     {

    5         Thread worker = new Thread(DoWork);

    6         worker.IsBackground = true;

    7         worker.Start();

    8 

    9         worker.Abort();

   10         worker.Join(1000);

   11     }

   12 

   13     static void DoWork()

   14     {

   15         try

   16         {

   17             Thread.Sleep(TimeSpan.FromSeconds(5));

   18         }

   19         finally

   20         {

   21             Console.WriteLine("cleanup executed");

   22         }

   23     }

   24 }

 
Diesmal wird der finally Block ausgeführt. Dies ist an der entsprechenden Konsolenausgabe zu erkennen.

Neben den Standard Foreground Threads und Background Threads existiert eine weitere Thread Variante, der BackgroundWorker. Diesen möchte ich Ihnen im nächsten Kapitel vorstellen.

 
BackgroundWorker
Der BackgroundWorker ist keine zusätzliche Threadvariante. Er ist eine Hilfsklasse zur einfachen Verwaltung eines Background Threads. Ein BackgrondWorker bietet folgende Features:

  • ProgressChanged Event mit ProgressPercentage Property, mit dem während der Threadausführung der Ausführungsfortschritt gemeldet werden kann
  • RunWorkerCompleted Event, welches nach Beenden der Threadmethode aufgerufen wird
  • Beide zuvor genannte Events werden so ausgelöst, dass in den Event Handlern WPF oder Windows Forms Controls direkt aktualisiert werden können
  • Exceptions der Threadmethode werden an das RunWorkerCompleted Event weitergegeben
  • Möglichkeit des Abbruchs des Threads mittels des kooperativen Abbruchmodels, bei welchem ein Abbruch signalisiert und in der Threadmethode ausgeführt wird

 

Der nachfolgende Beispielcode enthält die angesprochenen Features. Im Beispielcode habe ich die Zeilennummern weggelassen damit Sie diesen direkt kopieren und als Template zur Implementierung eines BackgroundWorker in Ihrer Anwendung nutzen können.

class Program

{

    static void Main(string[] args)

    {

        _backgroundWorker = new BackgroundWorker();

 

        //initialize background worker

        _backgroundWorker.WorkerReportsProgress = true;

        _backgroundWorker.WorkerSupportsCancellation = true;

 

        _backgroundWorker.DoWork += DoWork;

        _backgroundWorker.ProgressChanged += ProgressChanged;

        _backgroundWorker.RunWorkerCompleted += RunWorkerCompleted;

 

        //start background worker

        _backgroundWorker.RunWorkerAsync();

 

        //finish application on user input

        Console.ReadLine();

 

        //cancel worker thread

        if (_backgroundWorker.IsBusy)

        {

            _backgroundWorker.CancelAsync();

        }

    }

 

    static void DoWork(object sender, DoWorkEventArgs e)

    {

        int result = 0;

 

        _backgroundWorker.ReportProgress(0);

 

        for (int i = 0; i <= 100; i++)

        {

            //cancel thread?

            if (_backgroundWorker.CancellationPending)

            {

                e.Cancel = true;

                return;

            }

 

            //do work

            result = result + i;

 

            //report progress

            _backgroundWorker.ReportProgress(i);

        }

 

        e.Result = result;

    }

 

    private static void RunWorkerCompleted(

        object sender,

        RunWorkerCompletedEventArgs e)

    {

        if (e.Cancelled)

        {

            //thread was cancelled

        }

        else if (e.Error != null)

        {

            //exception occured within thread worker method

        }

        else

        {

            //thread finished successfull

            //e.Result contains the result of the worker method

        }

    }

 

    private static void ProgressChanged(

        object sender,

        ProgressChangedEventArgs e)

    {

        //update gui status

        //e.ProgressPercentage contains the worker method progress

    }

 

    private static BackgroundWorker _backgroundWorker;

}

 
Fazit

Der Hauptunterschied zwischen Foreground Threads und Background Threads liegt in deren Einfluss auf die Lebensdauer einer Applikation. Bei Background Threads sollte ein explizites Beenden der Threads beim Shutdown der Anwendung implementiert werden. Zusätzlich existiert die BackgroundWorker Klasse welche den Umgang mit einem Background Thread wesentlich vereinfacht.

Werbung
Dieser Beitrag wurde unter .NET, C#, Multithreading abgelegt und mit , , , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

2 Antworten zu Multithreading in C#, Teil 3: Foreground Thread vs. Background Thread vs. BackgroundWorker

  1. Pingback: Multithreading in C#, Teil 4: Individualisierter BackgroundWorker | coders corner

  2. Monty B schreibt:

    This was lovvely to read

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s