URL dieses Artikels:

zu Ausgabe: 5.2007
Untergrundarbeiter
Überraschend einfache Thread-Programmierung in C# mittels der BackgroundWorker-Komponente
Veikko Krypczyk
Der Artikel beschreibt eine einfache Möglichkeit, wie laufzeitintensive Prozesse im Hintergrund ausgeführt werden können. Mittels C# und der BackgroundWorker-Komponente aus dem .NET-2.0-Framework gelingt es, den Vorgang der Thread-Programmierung zu entzaubern und damit gewissermaßen zu standardisieren.

Trotz der enormen Entwicklungen im Bereich der Hardware sind gelegentlich laufzeitintensive Algorithmen durch eine Software auszuführen. Hierzu gehören beispielsweise aufwändige Berechnungen, Suchoperationen, Downloads, Datenbankoperationen oder Optimierungsprozesse. Insbesondere in Anwendungen, wo mathematische Näherungsverfahren zum Einsatz gelangen, ist eine Vielzahl von sich wiederholenden Verarbeitungsschritten (z.B. durch rekursive Algorithmen) auszuführen. Startet man ohne weitere Vorkehrungen derartige Algorithmen, so ist das Anwendungsprogramm nicht mehr für weitere Benutzeranforderungen reaktionsfähig. Dieser Zustand hält dann so lange an, bis die betreffende Prozedur vollständig abgearbeitet wurde und diese die Kontrolle an das aufrufende Programm zurückgegeben hat. Misslich ist die beschriebene Situation auch dahingehend, dass ein vorzeitiges Abbrechen des Vorgangs im Regelfall nicht möglich ist, da keine Interaktion über die Benutzeroberfläche zwischen Programm und Benutzer erfolgen kann. So mancher (ungeduldiger) Nutzer scheint in dieser Situation geneigt, das Programm über den Taskmanager gewaltsam abzubrechen, da er einen Softwarefehler (Programmabsturz) vermutet. Eine verbesserte Lösung für das beschriebene Szenario sieht beispielsweise wie folgt aus: Nachdem der betreffende Prozess durch den Anwender (bzw. durch die Software selbst) angestoßen wurde, erhält der Benutzer darüber eine Information (Meldung, Statuszeile usw.). Darüber hinaus wird jederzeit die Möglichkeit geboten, den Vorgang abzubrechen (Abbrechen-Button). Letzteres sollte aus zwei Gründen realisiert werden: Zum einen wird dem Anwender vermittelt, jederzeit die vollständige Kontrolle über die Vorgänge zu haben, und zum anderen ist auch nur so ein Beenden des Programms auf legale Weise möglich. Der eigentliche Prozess wird dann scheinbar im Hintergrund ausgeführt, d.h. die Interaktionsfähigkeit wird an das aufrufende Programm (und somit auch an die Benutzerschnittstelle) zurückgegeben. Ist es gewünscht, kann in Intervallen der Bearbeitungsstand der Hintergrundaktivität an das Hauptprogramm gemeldet werden. Als Darstellungsform bietet sich beispielsweise ein Fortschrittsbalken an. Diese Vorgehensweise ist vom theoretischen Ansatz her nicht neu und wird auch in professioneller Software angewendet. Das Stichwort zur Thematik lautet Thread-Programmierung und meint die Realisierung mehrerer nebenläufiger Prozesse. Deren programmiertechnische Umsetzung ist – unabhängig von der gewählten Programmiersprache – oftmals nicht das Lieblingsthema des Softwareentwicklers. Es erfordert einiges an Spezialwissen über das Betriebssystem und dessen Schnittstellen. Einige Hintergrundinformationen zu Threads und Prozessen und zwei Literaturverweise liefert die Textbox „Threads und Prozesse“.

Getreu dem Motto „Neu und besser“ findet sich im .NET 2.0 Framework ein komfortabler Lösungsvorschlag für das Problem. Gemeint ist die (nichtvisuelle) Komponente BackgroundWorker. Der Einsatz dieses Bausteins mittels der Programmiersprache C# wird nachfolgend dargestellt. Verwendet man Microsofts Visual Studio, so kann aus der Toolpalette das Symbol BackgroundWorker ausgewählt und auf das Formular gezogen werden. Selbstverständlich kann eine Instanz der Komponente auch manuell über den Quellcode erzeugt werden. Mit diesem unscheinbaren Schritt hat man sozusagen im Vorbeigehen einen Großteil der Arbeit erledigt. Der Vorteil wieder verwendbarer Software besteht darin, dass jetzt nur noch die Anpassung an die programmindividuellen Gegebenheiten stattfinden muss. Man muss sich keine Gedanken über die Anmeldung eines neuen Threads im Betriebssystem, über dessen Verwaltung und Synchronisation oder Beendigung machen. Sämtliche Funktionalität ist als „Blackbox“ verfügbar. Neben einer deutlich vereinfachten Implementierbarkeit, lassen sich hierüber die Laufzeitsicherheit und damit die Qualität der Software erhöhen. Der Entwickler sollte als Erstes denjenigen (laufzeitintensiven) Prozess ermitteln, welcher nebenläufig ausgeführt werden soll. An dieser Stelle sei ein Tipp aus der Praxis erlaubt: Der betreffende Quellcode des künftigen Hintergrundprozesses sollte vor der Integration in die Komponente ausführlich getestet werden. Grund dafür ist, dass das Debuggen eines Hintergrundprozesses zwar grundsätzlich möglich ist, jedoch fällt es dabei deutlich schwerer den Überblick zu behalten. Die Schnittstelle des Hintergrundprozesses ist nach dessen Implementierung und Test an die besonderen Erfordernisse der BackgroundWorker-Komponente anzupassen. Hierzu sind nur geringfügige Modifikationen notwendig.

… etwas Theorie
Nachfolgend wird auf die Einzelheiten zum Einsatz der Komponente eingegangen. Ein Studium der Online-Dokumentation [3] und speziell der Member (Eigenschaften, Methoden, Ereignisse) liefert die relevanten Informationen. Tabelle 1 gibt einen Überblick und eine Erklärung der wesentlichen Member der BackgroundWorker-Komponente.


Tabelle 1: Überblick über die wesentlichen Member der BackgroundWorker-Komponente



… ganz konkret
Nach der Vorstellung der Theorie soll die konkrete Vorgehensweise an einem Beispiel erläutert werden. Das Projekt der Beispielapplikation ist als Download von der Webseite des Verlages verfügbar [4]. Um die Angelegenheit einfach und nachvollziehbar zu gestalten, wurde folgende Vorgehensweise gewählt: Als laufzeitintensive Aktivität (hier als Hintergrundprozess bezeichnet) wurde lediglich eine Schleife erstellt, deren alleinige Aufgabe darin besteht, eine Pause von jeweils einer Sekunde durchzuführen. Diese Schleife wird zehnmal durchlaufen, sodass eine Gesamtverzögerung von 10 Sekunden entsteht. An der Stelle dieses (hier sinnlosen Vorganges) tritt dann später die konkrete laufzeitintensive Prozedur.

private void AufwaendigerVorgang1()
{
for (int i = 0; i < 10; i++)

Thread.Sleep(1000);
}
}


Wird dieser Hintergrundprozess aus der Anwendung ohne weitere Vorkehrungen aufgerufen, so kann die Anwendung 10 Sekunden auf keine weiteren Ereignisse reagieren. Diesen Zustand gilt es entsprechend zu verbessern. Im DoWork-Ereignis der BackgroundWorker-Komponente wird der Aufruf der Hintergrundaktivität vorgenommen. Dazu wird eine neue Instanz der Klasse BackgroundWorker erzeugt und diese als Objekt an den Hintergrundprozess mit übergeben. Diese wurde entsprechend den Erfordernissen erweitert (Listing 1).
Listing 1

private void AufwaendigerVorgang2(
BackgroundWorker worker, DoWorkEventArgs e)
{
for (int i = 0; i < 10; i++)
{
if (worker.CancellationPending==true)
{
e.Cancel = true;
break;
}
worker.ReportProgress(i * 10);
Thread.Sleep(1000);
}
}


Der Aufruf des Hintergrundprozesses erfolgt jetzt nicht mehr direkt, sondern über die RunWorkerAsync()-Methode. Abgebrochen wird mittels der CancelAsync()-Methode, d.h. hierdurch wird die Abbruchanforderung durch die BackgroundWorker-Komponente an den Hintergrundprozess übermittelt. In diesem selbst muss immer wieder auf das Vorhandensein einer Abbruchanforderung geprüft werden. Gleiches gilt für die Übermittlung des Bearbeitungsstandes. Den Unterschied zwischen der sequenziellen und der alternativen nebenläufigen Ausführung des Hintergrundprozesses zeigt Abbildung 1. Hier wird jeweils angenommen, dass sowohl der Hauptprozess als auch der laufzeitintensive Prozess jeweils eine Methode separater Klassen (Main und Background) ist. Der obere Teil der Abbildung zeigt, dass bei sequenzieller Verarbeitung die Instanz der Klasse Main auf die Antwort der Klasse Background warten muss. Im unteren Teil ist ersichtlich, dass die Klasse Main weiterhin „reaktionsfähig“ ist und regelmäßig eine Botschaft über den Verarbeitungsstand von  Background empfängt.


Abb. 1: Vergleich von sequenzieller und nebenläufiger Ausführung


Mittels der Testapplikation (Abb. 2) kann das Vorgehen leicht nachvollzogen werden. Der Quellcode (Auszüge) der vorgestellten Methoden und Ereignisse ist in Listing 3 zusammengefasst. Das Beispiel wurde bewusst schlank gehalten und auf das Wesentliche beschränkt.


Abb. 2: Formular der Testanwendung


Listing 2

private void buttonStartNormal_Click(…)
{
...
AufwaendigerVorgang1();
...
}
-------------------------
private void backgroundWorker_DoWork(…)
{
BackgroundWorker worker = sender as BackgroundWorker;
AufwaendigerVorgang2(worker, e);
}
-------------------------
private void buttonStartBackground_Click(…)
{
...
backgroundWorker1.RunWorkerAsync();
}
-------------------------
private void backgroundWorker1_RunWorkerCompleted(…)
{
if (e.Cancelled == true)
{
lblStatus.Text="Abbruch des Vorgangs";
}
else
{
lblStatus.Text = "Erfolgreich beendet";
}
...
}
-------------------------
private void buttonCancel_Click(...)
{
backgroundWorker1.CancelAsync();
...
}
-------------------------
private void backgroundWorker1_ProgressChanged(…)
{
progressBar1.Value = e.ProgressPercentage;
}


Zum Schluss
Mit C# und der Komponente BackgroundWorker gelingt es recht unkompliziert, laufzeitintensive Vorgänge im Hintergrund des Hauptprogramms ausführen zu lassen. Die einfache Integration und Konfiguration des Bausteins macht dieses – sonst als komplex eingestufte – Thema alltagstauglich für den Softwareentwickler. Der Einbau, auch in bestehenden Quellcode, ist unkritisch. Es muss lediglich die Methode zum Aufruf des Hintergrundprozesses entsprechend modifiziert werden und eine regelmäßige Überprüfung auf eine Abbruchanforderung stattfinden. Zum leichteren Verständnis bietet es sich an, ein eigenes Beispiel zu konstruieren. Dazu kann die Beispielapplikation modifiziert werden, indem ein konkreter zeitintensiver Vorgang (statt der Sinnlosschleife) eingebaut wird.

Veikko Krypczyk hat Betriebswirtschaftslehre u.a. in der Fachrichtung Wirtschaftsinformatik studiert. Nebenberuflich ist er als Softwareentwickler und Fachautor für Themen der IT tätig. Er beschäftigt sich seit längerem mit der Softwareentwicklung, u.a. mit C# und dem .NET Framework. Lob und Kritik senden Sie per E-Mail an veikko2000@yahoo.de.

© 2004 Software & Support Verlag GmbH. Vervielfältigung nur mit Genehmigung des Verlags. Fragen?