Mach mal Action!

Kürzlich fand ich im Xojo-Forum eine absolut aufgreifenswerte Frage, weil wir hier einen Fall haben, wo die Benutzerfreundlichkeit von Xojo den Programmierer in die Irre schicken kann. Ein sehr guter Anlass also, im Interesse des Blog-Anspruchs aufklärerisch tätig zu sein. Zumal dies hier ein Problem ist, das mich in der Anfangszeit ebenso in die Ratlosigkeit entließ. Weiß der Geier, welcher psychologische Effekt dazu führt, dass diese Erinnerung verdrängt und niemals durch einen Artikel gewürdigt wurde. Wohlan nun, und ganz von vorne:

Ein sehr häufig benötigtes Objekt in Xojo ist ein Timer. Den kann man nicht nur trefflich verwenden, um sich regelmäßig wiederholende Aktionen zu starten, er ist auch nützlich, um dem Betriebssystem Zeit zu geben, zwischen engen Programmschleifen nachzuschauen, ob vielleicht das eine oder andere Steuerelement neu gezeichnet werden sollte (und dies auch zu tun). Und, ganz wichtig: Ein Timer bietet die Möglichkeit, von einem Hintergrundprozess wieder auf den Main-Thread zu hüpfen.

Carbon, Cocoa, NeXT & OS X

An dieser Stelle ein bisschen Historie, die Sie gerne überblättern können, wenn Ihnen die obigen Aussagen einleuchten. Falls aber nicht:

Wenn Sie dem Mac schon länger zugewandt sein sollten, dann kennen Sie noch die große historische Krise, als es darum ging, das altehrwürdige Mac OS 9 zu einem modernen Betriebssystem zu machen. Echtes, präemptives Multitasking war damals nicht möglich: Das System erteilte den Tasks, also den Aufgaben, die sich für Hintergrundverarbeitung angemeldet hatten, kurze Zeiteinheiten, in denen jeweils der aktive Task etwas tun durfte, und wandte sich dann dem nächsten zu. Naheliegend, dass das nicht die optimale Herangehensweise ist, um MultiThread-fähige und erst recht MultiCore-CPUs auszunutzen. Die können, wie ihre Übersetzung nahelegt, mehrere Prozesse echt parallel verarbeiten, und besitzen darüberhinaus mehrere Kerne, also mehrere CPUs, die parallel in einem Gehäuse werkeln.

Die Geschichte abgekürzt: Apple bekam’s nicht gebacken, und in einem Gnadenmoment der Geschichte holte man Steve Jobs zurück an Bord, adaptierte dessen NeXT-Betriebsssystem und baute es zweigleisig aus: Einmal mit dem Carbon-Framework, mehr oder weniger OS 9 im OS X-Gewand, und einmal mit Cocoa, dem puren OS X und damit all die feinen Multifeatures unterstützend.

So ein Task wie oben erwähnt, also ein Hintergrundprozess, das ist in Xojo ein Thread: Mehr oder weniger eine normale Methode, die Sie aber separat anlegen und mit einem Thread.Run starten, woraufhin sie dann im Hintergrund ausgeführt wird, während das Programm im Main-Thread andere Dinge tut.

Der Main-Thread ist, kurz gesagt, ein mit besonderen Rechten ausgestatter Thread der CPU. In der Regel wird alles, was Sie nicht in einen Xojo-Thread packen, auf ebendiesem Main-Thread ausgeführt. Sie haben quasi doch wieder einen Hauptprozess, wie anno dunnemals, in dem alles brav hintereinander ausgeführt wird, und Hintergrund- sowie Parallelprozesse nur dort, wo Sie sie auch explizit anlegen.

Zu den besonderen Privilegien des Main-Threads gehört das Recht, die Benutzeroberfläche zu verändern – einen Text auszugeben, eine Listbox zu aktualisieren, ein Fenster neuzuzeichnen … was auch immer sich auf dem Bildschirm abspielt. Der Grund dahinter leuchtet schnell ein, wenn Sie sich überlegen, wie definiert der Bildchirmzustand wäre, wenn mehrere parallele Threads gleichzeitig ein Steuerelement nach ihren Vorstellungen ändern wollen. Früher war das wie gesagt anders, da liefen die Aufgaben nur scheinbar parallel, in Wirklichkeit aber in kleinen Zeitscheibchen nacheinander.

Wenn Sie nun versuchen, aus einem Xojo-Thread heraus eine Methode aufzurufen, die auf die Graphische Benutzeroberfläche zugreift, werden Sie mit einer Exception belohnt: Ihr Programm stürzt ab, da das nicht erlaubt ist. Denn, ganz wichtig:

Eine Methode wird immer auf dem Thread ausgeführt, von dem aus sie aufgerufen wurde.

Vom Haupt- auf einen Hintergrundthread gelangen Sie, indem Sie einen Thread definieren und ihn mit Thread.Run starten. Aber wie gelangen Sie von dort wieder zurück auf den Hauptthread, um z.B. das Ergebnis der langwierigen Hintergrundberechnung auszugeben?

Die Antwort, und das ist dieses langen Einschubs kurzer Sinn, lautet: Nehmen Sie einen Timer, denn:

Der Action-Event eines Timers wird immer auf dem Main-Thread ausgeführt.

Timer auf die graphische Art

Willkommen zurück, falls Sie den Einschub übersprungen haben!

Sehr hübsch und bequem in Xojo: Einen Timer können Sie einfach von der Bibliothek auf Ihr Layout ziehen. Dann ein Rechtsklick auf ihn, den Action-Eventhandler hinzufügen und seine Eigenschaften im Inspector setzen.

Timer
Unterhalb des Fensters: Ein ins Layout gezogenes Timer-Objekt.

Was aber, wenn Sie gar kein Fenster haben? Der Timer soll Bestandteil einer eigenen Klasse oder der App-Instanz sein und völlig unabhängig von graphischen Gegebenheiten funktionieren?

 

Timer per Code

Die halbe Antwort sehen Sie ebenfalls im Screenshot, nämlich im Navigator: Dort habe ich eine Property myTimer als Timer angelegt. Deren Eigenschaften kann ich jetzt freilich nicht im Inspector setzen; das funktioniert in dieser Form nur mit Steuerelementen.

Ich kann sie aber an anderer Stelle definieren, z.B. im Open-Event der App:

 mytimer = new timer
 mytimer.Period = 0

Die erste Zeile benötige ich, um den Timer zu initialisieren, und mit der zweiten mache ich ihn zu einem verzögerungsfreien Timer. Mit

mytimer.Mode = timer.ModeSingle

löse ich ihn aus. Aber was löse ich aus? Ich habe ja keine Möglichkeit, in seinen Action-Event-Handler zu schreiben.
Die Antwort dazu ist ganz einfach und nennt sich AddHandler. Damit verbiege ich einen Event auf eine Methode.

AddHandler myTimer.action, AddressOf TimerActionMethod

ist der Befehl, mit dem ich das machen kann. Der Action-Event von myTimer wird also auf eine Methode TimerActionMethod verbogen. Addhandler liefert automatisch den Sender mit, in diesem Fall also myTimer. TimerActionMethode sieht daher bei mir so aus:

Private Sub TimerActionMethod(t as timer)
 msgbox "Timer fired!"
 #pragma unused t
End Sub

Da ich den Timer nicht benötige, erkläre ich ihn mit dem #Pragma unused wieder weg – der Compiler weist mich sonst darauf hin, dass ich völlig unnötig eine Property übergebe. Ich könnte also diese Methode auch für mehrere Timer zugleich verwenden und je nach übergebenem Timer-Objekt unterschiedlich reagieren.

AddHandler funktioniert bei allen Events. Egal was Sie per Code definieren und initialisieren: Ein Fenster, ein NotificationCenter, ein anderes Steuerelement: Wenn es Events besitzt, können Sie sie via AddHandler auf eigene Methoden verbiegen. Und damit z.B. sehr schnelle Kommunikationsstrukturen schaffen.

Zu AddHandler gibt es ein Gegenstück: RemoveHandler. Seine Parameter sind dieselben. Um keine Speicherlecks zu verursachen, indem z.B. ein verbogener Action-Event ein Objekt im Speicher lebendig hält, das Sie eigentlich gar nicht mehr benötigen, sollten Sie sich angewöhnen, an geeigneter Stelle auch RemoveHandler aufzurufen. Hier in diesem Beispiel also eventuell im Close-Event-Handler der App:

 RemoveHandler myTimer.action, AddressOf TimerActionMethod

 

Darf’s noch ein Stückchen schneller sein?

Und wo wir schon dabei sind: Das neue Xojo-Framework bringt bei Einmal-Timern, also solchen, die nur einmal aufgerufen werden, etwa um zwischen zwei engen Schleifen Luft für die Aktualisierung eines Fortschrittsbalkens zu schaffen oder um auf den Main-Thread umzusteigen, noch eine Verfeinerung, die Ihnen das Anlegen eines Timer-Objekts völlig erspart. Das ist die Klassenmethode Xojo.Core.Timer.CallLater

Die Anwendung ist trivial: Als Klassenmethode benötigen Sie keine Instanz. Um von einem Thread aus eine Main-Thread-Aktion anzustoßen, reicht es, ein

Xojo.core.Timer.CallLater 0, AddressOf CoreTimerActionMethod

einzubauen, und die Methode CoreTimerActionMethod wird auf dem Main-Thread aufgerufen. CoreTimerActionMethod erhält keine Übergabeparameter – CallLater ist ja wie gesagt eine Klassenmethode, die ohne eigene Timer-Instanz funktioniert.

Eigentlich ganz trivial, oder? Falls Sie direkt losprobieren wollen: Hier finden Sie das kleine Projekt dazu.

 

Für sehr Interessierte

Im technischen Sinne ist ein Xojo-Thread kein echter präemptiver Thread. Um wirkliches paralleles Multitasking zu verwenden, bedarf es anderer Lösungen. Markus Winter hat dazu im XDev-Magazin eine Artikelserie veröffentlicht.

 

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