Site logo
Site logo
Programmieren aus Leidenschaft
Programmieren aus Leidenschaft

Threads mit Events und invoke


Wie wir im letzten Kapitel gelernt haben, ist es gar nicht so schwer aus einem Thread heraus mit dem Hauptprogramm zu kommunizieren. Events helfen dabei sehr. Leider gibt es dennoch ein paar Fallstricke, die uns das Leben schwer machen können. Aber am Ende dieses Kapitels werden wir wissen, wie man sie umgeht.

Ändern wir nur eine Programmzeile der Anwendung, die wir im letzten Kapitel erstellt haben.

MessageBox.Show(e.ToString());

Ändern wir zu

this.Text = e.ToString();

Es sieht ganz einfach aus. Statt in eine Messagebox, schreiben wir das Ergebnis einfach in die Titelzeile unseres Fensters. Dieser Vorgang scheint keine größere Herausforderung zu sein, trotzdem bekommen wir bei Ausführung dieses Programmes eine Fehlermeldung.

Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement Form1 erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.

Offensichtlich gefällt es unserem Formular nicht, wenn ein anderer Thread versucht hier Änderungen vorzunehmen. Wir sollten auch daran denken, dass wir ja schon einen Thread haben. Das ist nämlich unser Hauptprogramm, welches so ganz nebenbei auch unser Formular erzeugt hat. Diesem Hauptthread mißfällt es also, wenn ein anderer Thread versucht hier Änderungen vorzunehmen. Das ist sicherlich logisch nachvollziehbar, denn es ist ja durchaus möglich, dass wir mehr als einen weiteren Thread haben. Würden die alle versuchen in die Titelleiste unseres Formulars zu schreiben, würde das sofort im Chaos enden und man wäre niemals sicher welcher Text denn nun in der Titelleiste stünde.

Dieses Problem lässt sich auf zwei Arten lösen. Die erste ist sehr einfach. Wir schreiben einfach in den Konstruktor unseres Formulars:

CheckForIllegalCrossThreadCalls = false;

Dieser Befehl löst nicht unser Problem, aber er verhindert das Fehlermeldungen ausgegeben werden. Starten wir unser Programm, dann sehen wir, dass es fehlerfrei ausgeführt wird und das Rechenergebnis unseres Threads in der Titelleiste des Formulars ausgegeben wird.

Sicherlich ist das Unterdrücken von Fehlermeldungen nicht die beste Lösung, obwohl sie gut funktioniert. Besser wäre es, wenn wir den Hauptthread dazu bewegen könnten unser Ergebnis in die Titelleiste zu schreiben. Unser Nebenthread darf dies ja nicht.

Das erste Problem besteht darin, zu prüfen, ob der Thread, der in die Titelleiste schreiben will, dies auch darf. Wir sollten nicht vergessen, dass wir die „myTC_fertig“-Methode auch ohne Threads und Events immer noch direkt vom Hauptprogramm aufrufen können. Wie aber überprüfen wir, von welchem Thread der Aufruf kommt? Dafür gibt es die „InvokeRequired“-Eigenschaft unseres Formulars.

„InvokeRequired“ - was übersetzt etwa soviel wie „Aufruf benötigt“ heißt - gibt uns Auskunft darüber, von welchem Thread die Methode aufgerufen wird. Wir können daher unsere Methode wie folgt ändern:

void myTC_fertig(int e)
{
   if(this.InvokeRequired == true)
   {

   }
   else
   {
      this.Text = e.ToString();
   }
}


Kommt der Methodenaufruf aus demselben Thread wie der Thread, der das Formular erzeugt hat, können wir gefahrlos in die Titelleiste schreiben. Was aber, wenn der Aufruf aus einem anderen Thread kommt? Hier müssen wir noch etwas tun.

Wie erkennt das Programm eigentlich, dass ein Thread das Formular ändern darf, ein Anderer aber nicht? Die Antwort ist einfach. Nur der Thread der ein Steuerelement erstellt hat, darf es auch ändern. Um genau zu sein, dieser Thread besitzt das Fensterhandle des Steuerelements.

Wie bekommen wir aber das benötigte Fensterhandle? Dafür gibt es den Befehl „invoke“. Damit können wir Delegaten in einem bestimmten Thread aufrufen. Ein „this.Invoke“ würde den Delegaten im Hauptthread ausführen. Er würde also das Fensterhandle des Hauptthreads bekommen.

Als erstes benötigen wir einen Delegaten:

delegate void myTC_fertigCallback(int e);

Dann ändern wir unsere Methode wie folgt:

if(this.InvokeRequired == true)
{
   myTC_fertigCallback callback = new myTC_fertigCallback(myTC_fertig);
   this.Invoke(callback, new object[] { e });
}


Wir erzeugen hier einen Delegaten auf die Methode „myTC_fertig“. Dass dies die Methode ist, in der wir uns gerade befinden, mag etwas seltsam erscheinen, ergibt aber mit dem nächsten Befehl Sinn. Durch das „invoke“ rufen wir unsere Methode erneut auf, haben aber dieses Mal das Fensterhandle des Threads, der unser Formular erstellt hat. Was wiederum dazu führt, dass „InvokeRequired“ falsch ist und der else-Zweig unserer Abfrage ausgeführt wird. Wir landen also wieder bei

this.Text = e.ToString();

Durch unseren Thread und einen Event rufen wir eine Methode auf, die in die Titelleiste schreiben soll. Durch „InvokeRequired“ erfahren wir aber, dass wir dies gar nicht dürfen, weil unser Thread nicht das richtige Fensterhandle besitzt. Also erzeugen wir uns mit Hilfe eines Delegaten einen sogenannten „callback“, der die gleiche Methode noch einmal aufruft. Dieses Mal aber durch „Invoke“ das benötigte Fensterhandle bekommt. Jetzt wird auch endlich der Text in die Titelleiste geschrieben.

Diese Vorgehensweise erscheint auf den ersten Blick sehr kompliziert und ist, das will ich gerne zugeben, gewöhnungsbedürftig. Aber in komplexen Programmen ist es unbedingt erforderlich, Threads auf diese Art zu organisieren. Es gibt nämlich noch weitere Probleme wie wir im nächsten Kapitel sehen werden.