C#: BeginInvoke

C#: BeginInvoke

Mit BeginInvoke lassen sich Threads aus dem Threadpool nutzen um eine bestimmte Funktion asynchron zu erledigen. Im Falle einer Forms Anwendung lassen sich mit BeginInvoke interessante Effekte erzielen.

Der Windows Threadpool ist eine Ansammlung von Threads, die bereits aufgesetzt sind und nur noch loslaufen müssen. Der Vorteil dieser Threads ist eben diese Tatsache: das Erstellen eines Threads ist relativ aufwendig, ein eigener Stack muß initialisiert werden (was dann u. a. auch die Anzahl der auf einem System parallel laufender Threads beschränkt: jeder Thread braucht ca. 64k an Speicher, wenn explizit das Minimum angefordert wird, sonst ca. 1M), Systemobjekte zur Verwaltung erzeugt werden usw. Das kann man mit einem Thread aus dem Threadpool umgehen. Gerade für einfache, kurze Aufgaben ist dieser Pool ideal. Für längere und massiv parallele Aufgaben hingegen ist der Pool nicht geeignet, da es lediglich (als standard) zwischen 25 und 250 Threads (pro CPU) (je nach .net Framework Version) in diesem Pool gibt.

Delegate Invoke ist eine Möglichkeit, ein Stück Code/eine Funktion synchron auszuführen, d. h. die Funktion wird wie ein normaler Funktionsaufruf ausgeführt.

Delegate BeginInvoke benutzt den Threadpool, um eine Funktion asynchron auszuführen, z. B. um eine GUI antwortbereit zu halten.

Dies ist das Standardverhalten. Im Falle von Controls in WinForm sieht das ganze jedoch anders aus: hier wird nicht der Threadpool benutzt, sonder die Aufrufe werden in die MessageLoop der aktuellen GUI eingefügt (es gibt nur eine MessageLoop pro Applikation).

WinForm BeginInvoke benutzt die PostMessage Funktion, um den auszuführenden Code in die MessageLoop asynchron einzufügen, d. h. sie wird am Ende der Queue eingefügt, und es wird nicht darauf gewartet, bis die Nachricht abgearbeitet ist.

WinForm Invoke benutzt nicht etwa den synchronen Aufruf via SenMessage (wartet auf Antwort), obwohl das Verhalten so ist wie erwartet, der Code wird synchron ausgeführt. Es wird eher etwas in der Art von EndInvoke(BeginInvoke(method, null)) ausgeführt.

Mit der WinForm Variante von BeginInvoke lassen sich z. B. 2 einfache Probleme lösen:

In Antwort auf ein Load Event des Forms möchte ich den Fokus auf ein Element setzen.

1
2
3
4
5
          private void Form1_Load(object sender, EventArgs e)
          {
              // execute once the form is loaded: insert into message queue
              this.BeginInvoke((MethodInvoker)delegate { textBoxSearch.Focus(); });
          }

Sobald ich in eine Zelle klicke (bekommt Focus) möchte ich den Text dieser TextBox selektieren. Dies funktioniert standardmäßig per Tab, beim Klicken wird jedoch nach dem Focus-Event ein weiteres Maus-Event ausgelöst, dass die Auswahl wieder aufhebt.

1
2
3
4
5
          private void textBox1_Enter(object sender, EventArgs e)
          {
              // enqueue in message queue. so this is processed after any mouse event might have invalidated the selectall
              this.BeginInvoke((MethodInvoker)delegate { textBoxSearch.SelectAll(); });
          }

Wie hier gesehen funktioniert das besonders schön zusammen mit einer anonymen Methode oder einem lambda Ausdruck.

Bei WinForms BeginInvoke sollte man aufpassen, dass man die MessageLoop nicht überlastet – bestimmte Nachrichten wie WM_PAINT oder WM_TIMER werden wohl nur bei einer leeren Queue von der MessageLoop synthetisiert (diese werden also in der Queue nie zu finden sein, stattdessen werden sie zu gegebener Zeit von der Loop selber erzeugt). Bei einer stets vollen Queue werden diese Nachrichten also nie gesendet, dementsprechend updatet die GUI nicht mehr und evtl. benutzte Timer laufen nicht mehr.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.