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.
… 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.
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.
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.



