Mit spitzer Nadel

oder Wie funktioniert das eigentlich mit den Threads?

Ein Problem, in das man beim Programmieren mit Xojo schnell mal geraten kann, sind die tight loops – Programmschleifen, die die CPU beschäftigt halten und dafür sorgen, dass das Betriebssystem keine Zeit bekommt, Veränderungen der Grafischen Benutzeroberfläche – der GUI – anzuzeigen, weil der Programmcode ihm keine Luft gibt. Das Resultat sind die üblichen Signalisierungen des Beschäftigtseins – der bunte Beachball kreiselt auf dem Mac, unter Windows wird ein Kreis neben dem Mauszeiger angezeigt. Dumm für den Benutzer, denn er ist sich unsicher, ob wirklich noch etwas passiert oder das Programm bereits ins Datennirvana übergetreten ist.

Wenn Sie dies als unfair empfinden, gebe ich Ihnen ganz recht: Die CPU heute ist ja eine Mehrkern-CPU, und jeder Kern ist auch noch in der Lage, mehrere Threads, also mehrere Programmstränge, parallel zu bearbeiten.

Nun gibt es davon aber stets einen bevorzugten Thread: den Main Thread. Das ist der einzige, von dem auf den aktuellen Betriebssystemen aus die Benutzeroberfläche verändert werden darf. Warum das so ist, habe ich an anderer Stelle beschrieben. (Hier also eine etwas gründlichere Darstellung des dort schon Beschriebenen.) Da Sie selbst mit Ihrem üblichen Xojo-Programm Steuerelemente, Fenster etc. manipulieren können, liegt der Konflikt auf der Hand: Xojo-Code benutzt üblicherweise den Main Thread, und das Betriebssystem aus beschriebenen Gründen ebenso.

Wenn Ihr Code z.B. eine Progressbar verändert, aber sofort mit der Ausführung weitermacht, dann kommt die Veränderung zwar beim Betriebssystem an, aber es hat keine Zeit, um sich um die Umsetzung zu kümmern. Deshalb das Zeichen der Überlastung wie oben beschrieben.

Als Anschauungsmaterial:

  • Erstellen Sie ein neues Desktop-Projekt, legen Sie eine Progressbar über das Fenster und spendieren Sie letztem noch einen Button (in dieser Ansicht schon mal zwei):

Tread Window

  • Dem Button – hier mit „Threadlos“ betitelt, geben Sie den Action-Eventhandler
For q As Integer = 1 To 10000
 ProgressBar1.Value = q
 For p As Integer = 0 To 100000
 
 Next
next
  • Stellen Sie im Inspector noch den Wert des Progressbars – die Property Value – auf 0 und das Maximum auf 10000.

Führen Sie das ganze im 32 Bit-Modus aus! Da der LLVM, der bei 64 Bit anspringt, den Programmcode optimiert, kann es sein, dass ansonsten die sinnlose zweite Schleife, die einfach dazu dient, Zeit zu verbraten, einfach wegoptimiert wird. Dann wär der Balken sofort voll. So aber werden Sie nach Knopfklick ein Weilchen das betriebssystemübliche „Bitte warten“ sehen, und am Ende ist der Balken voll.

Das ganze noch einmal – mit Thread

Für Probleme dieser Art gibt es in Xojo die Thread-Klasse. Ein Thread ist dabei leider – zumindest zurzeit – kein sogenannter präemptiver Thread (lesen Sie das einfach als „echter paralleler Thread“, so wie man sich das bei einem Mehr-CPU-System vorstellt), sondern ein kooperativer. Das bedeutet, wie bei alten Betriebssystemen wie etwa Mac OS 9 wird hier die Parallelverarbeitung nur vorgegaukelt – das System vergibt die Rechenzeit scheibchenweise und wechselt schnell zwischen Haupt- und Nebenthread hin und her.

Echtes Multiprocessing ist, nebenbei gesagt, auch in Xojo möglich, aber zurzeit ein wenig kompliziert. Es sind aber auch schon Codebestandteile gesichtet worden, die darauf hinweisen, dass Abhilfe in Planung ist. Aber das alles ist eine andere Geschichte und soll ein andermal erzählt werden.

Auch für Xojo-Threads gilt allerdings die Prämisse, dass sie nicht auf Bestandteile der GUI zugreifen dürfen. Es würde nicht reichen, den obigen Code einfach in den Run-Eventhandler eines Threads zu verschieben, jedenfalls nicht, ohne die berüchtigte ThreadAccessingUI-Exception hervorzukitzeln.

Man muss vom Thread vielmehr auf den Main Thread hüpfen, um absturzfrei vorzugehen. Und das geschieht wie folgt:

  • Spendieren Sie dem Fenster noch einen zweiten Button – siehe oben, bei mir trägt er die Caption „mit Thread“.
  •  Holen Sie aus der Library einen Thread und platzieren Sie ihn im grauen Bereich am Fuß des Layout-Editors, also unter dem Fenster.
  • Der neue Button bekommt den Action-Event
Thread1.Run
  • Rechtsklicken Sie auf das Thread-Symbol und geben Sie dem Thread seinen Run-Eventhandler. Der Code dafür lautet wie folgt:
For q As Integer = 1 To 10000
 Xojo.core.timer.CallLater 0, AddressOf SetProgressBar, q
 For p As Integer = 0 To 100000
 
 Next
Next
  • Nun benötigt das Fenster noch die neue Methode:
Public Sub SetProgressBar(value as Auto)
 ProgressBar1.Value = value
End Sub

Und das war’s auch schon. Probieren Sie es einmal aus, und dann folgt die Erklärung:

Xojo.Core.Timer.Calllater ist eine Klassenmethode der Xojo-Framework-Timer, d.h., Sie müssen keinen Timer dafür anlegen. Nach einer Wartezeit von 0 Millisekunden wird die Methode aufgerufen, die Sie als Delegate angeben – also via AddressOf. Und dies geschieht grundsätzlich auf dem Main Thread.

Wenn diese Methode Parameter erwartet, müssen diese als Auto übergeben werden. D.h., Sie müssen eigentlich nichts weiter tun als darauf zu achten, dass die Methode auch einen entsprechenden Wert bekommt – SetProgressbar also nicht versehentlich mit einem String versorgen.

Soweit alles klar? Dann noch ein paar Feinheiten:

Weitere Thread-Features

Priority

Wenn Sie das Thread-Objekt im Inspector betrachten, können Sie dort noch die Priorität einstellen. Standardmäßig steht sie bei 5. Das ist auch der Wert, den der Main Thread besitzt. Springt also der Scheduler an, der Verwalter über Thread-Zeitscheiben, vergibt er diese anhand dieser Priorität. Bei einem Wert von 5 also bekommen Main- und Nebenthread gleichviel Rechenzeit.

Wollen Sie den Thread beschleunigen, dann erhöhen Sie einfach dessen Priorität. Probieren Sie mal mehrere Werte aus. Geben Sie zu hohe Werte ein, wird wiederum die GUI blockiert.

Sleep

Mittels Thread.Sleep (Millisekunden As Integer) können Sie einen Thread eine Weile schlummern lassen.

Suspend

pausiert die Ausführung eines Threads, und

Resume

lässt ihn wieder weiterarbeiten.

Kill

schließlich beendet einen Thread komplett.

Über all diese Zustände informiert Sie die Property

State

des Threads. Der Integerwert bedeutet:

  • 0 = Running (der Thread läuft also gerade)
  • 1 = Waiting (wenn er gerade blockiert ist. Diesen Sonderfall schauen wir uns bei Gelegenheit mal gemeinsam an)
  • 2 = Suspended – siehe Suspend
  • 3 = Sleeping – siehe Sleep
  • 4 = Not Running – er läuft nicht

StackSize

zu guter Letzt gibt Ihnen die Möglichkeit, dem Thread zusätzlichen Speicher für eigene Variablen zur Verfügung zu stellen. Sollte Ihr Thread sehr, sehr (sehr!) viele interne Variablen verwenden, dann eventuell werden Sie mal die Notwendigkeit verspüren, diesen Wert, dessen Vorgabe 0 = Standardwert bedeutet (laut Referenz 512 kB unter macOs und 1 MB unter Windows) zu verändern. Mir ist das noch nicht passiert.

Alle Fragen geklärt? Dann gutes Fädeln!

Hier ist das Beispielprojekt zum Spielen: Thread

 

 

 

 

 

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 )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

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

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s