Alte Projekte modernisieren – Teil I

Die böse ThreadAccessingUIException

Wenn Sie schon länger in Xojo programmieren, dann finden sich in Ihrem Code-Fundus mit großer Wahrscheinlichkeit Projekte, die heute unter einer aktuellen Xojo-Version nicht mehr laufen wollen. Oder aber Sie stolpern über ein Projekt noch aus REALBasic-Zeiten und würden es gerne einmal ausprobieren. Dann liegen auf dem Weg gerne ein paar typische Hindernisse, die ich in dieser Reihe einmal kurz beleuchten möchte.

Als erstes Beispielprojekt habe ich mir Langton’s Ant von Roger Meier’s OpenSource-Seite ausgesucht. Langtons Ameise ist eine Mathematik-Simulation ähnlich dem altbekannten Game of Life (für das auf obiger Seite auch ein Beispielprojekt existiert): Eine Ameise, definiert durch einen Bildschirm-Punkt, schaut sich den Punkt an, auf dem sie steht. ist er weiß, dreht sie sich 90° nach rechts, andernfalls (schwarzes Feld) 90° links. Dann invertiert sie die Farbe des Felds und geht einen Punkt in „Blickrichtung“ weiter.

langtonsantatwork

Das Interessante an diesem einfachen Algorithmus ist, dass sich die Ameise nach geraumer Zeit der unkontrollierten Bewegung für eine Richtung entscheidet und beginnt, eine Ameisenstraße zu bauen – aus dem Zufallsmuster wird ein rhythmisch wiederholtes. Das Beispiel von Roger Meier erlaubt auch, mit der Maus auf den Bildschirm zu zeichnen und der Ameise damit Hindernisse in den Weg zu legen.

Also lassen Sie uns mal loslegen:

  • Laden Sie bitte von obiger Site das Beispielprojekt herunter und entzippen Sie es.

langtons-ant-site

Die Projektdatei endet auf „.rb“ – REALBasic. Je nach Ihrer Xojo-Version kann es sein, dass ein Doppelklick darauf nicht mehr die Datei in die IDE lädt. Sie können sie aber per Drag & Drop auf das Xojo-Programmicon ziehen, dann erinnert sich Xojo wieder seines Ahnen und öffnet auch so ein altes Projekt.

Statt den Code und die Projektobjekte widerspruchslos anzuzeigen, begrüßt Sie Xojo nun mit einem Problemfenster:

issues-ant

Der erste Eintrag weist auf eine automatische Aktualisierung hin: Das Framework wurde für den Mac auf Cocoa aktualisiert, nicht mehr das veraltete Carbon-Framework, das die Brücke für OS 9-Programme auf OS X darstellte.

Die zweite Zeile trägt eine Checkbox, und diese sollten Sie anklicken (oder „Check all“ unten wählen). macOS-Programme – ebenso wie iOS-Apps – benötigen eine Bundle ID, eine eindeutige Kennung der Anwendung.

  • Diese wird automatisch gesetzt, wenn Sie nach Häkchensetzen „Resolve“ anklicken – also „Löse die Probleme“. Den Erfolg signalisiert dann ein grünes Häkchen statt des roten Ausrufezeichens.
  • Klicken Sie dann auf „Close“, um endlich das Projekt zu sehen.

Wenn Sie zunächst im Navigator „OS X“ anwählen, zeigt Ihnen der Inspector auch den Erfolg: Der Bundle-Identifier lautet auf „com.mycompany.LangtonsAnt“. Gefällt Ihnen das nicht (und falls Sie überhaupt für macOS entwickeln), können Sie ihn hier auch manuell ändern.

bundeid-set

  • Nun einfach mal „Run“ geklickt und gefreut: Ein einziger Fehler wird angemerkt!

newpicture

Klicken Sie auf diese Fehlermeldung, und Sie landen im Code-Editor an der entsprechenden Stelle: im Open-Eventhandler von Window1.

Die Methode NewPicture gibt es nicht mehr. Sie kann aber 1:1 durch den Befehl New Picture ersetzt werden.

  • Setzen Sie einfach ein Leerzeichen zwischen New und Picture und starten Sie einen neuen Kompilierungsversuch.

Schon besser, oder? Das Hauptfenster des Programms erscheint. Allerdings wird die Freude nicht lange währen: Wenn Sie auf „Go“ klicken, landen Sie bald im Debugger, und drücken Sie dort auf Fortsetzen, erscheint eine Fehlermeldung:

threadaccessinguiexceptionerror

Das wird Ihnen recht häufig bei alten Projekten passieren, die Threads verwenden. Unter älteren Betriebssystemen, wie Mac OS 9 bzw. Carbon-Anwendungen, war es möglich, aus einem Thread heraus Objekte der grafischen Benutzeroberfläche zu manipulieren. Das durfte man, weil Threads damals keine echten Hintergrundprozesse waren, sondern das Betriebssystem immer scheibchenweise Ausführungszeit für Threads bereitstellte. Auch wenn es so wirkte, also ob mehrere Prozesse parallel liefen: In Wirklichkeit war’s nur der schnelle Wechsel zwischen mehreren Prozessen, von denen aber immer nur einer gerade (zeitweilig) aktiv war.

Durch diese Ausführung ließ sich der Zustand der Benutzeroberfläche mehr oder weniger eindeutig definieren (in der Praxis konnte es holperig werden, denn zwischen zwei Manipulationsschritten eines Threads könnte theoretisch eine Veränderung eines anderen Threads bearbeitet worden sein). Weshalb es bei modernen Betriebssystemen, die über echte parallele Threads verfügen, verboten ist, aus einem Thread auf die GUI zuzugreifen.

  • Führen Sie das Programm noch einmal aus und klicken Sie wieder auf „Go“, aber bleiben Sie im Debugger:

threadstep1

Sie sehen hier, wo die Exception aufgetreten ist: In der Methode Window1.Draw. Ein Canvas.Graphics.Drawpicture ist aber unverdächtig – wichtig ist, von wo diese Methode aufgerufen wurde.

  • Klicken Sie auf die zweite Zeile im Debugger-Stack-Abschnitt, die mit Window1.Ant1.Run bezeichnet ist.

In der Stack-Übersicht sehen Sie die Hierarchie des aufrufenden Codes, von „jetzt“ (ganz oben) ggf. immer weiter nach unten in die Vergangenheit gehend. Window1.Ant1.Run hat also Window1.Draw aufgerufen:

threadstep2

Was Sie hier auch sehen. Ant1 ist aber eine Instanz der Klasse Ant, und diese ist ein Thread. Hier ist also der Verursacher zu suchen.

Man könnte meinen, dass ein Zugriff dieser Art unkritisch wäre: Die Window1.Draw-Methode ist ja Bestandteil des Fensters. Wichtig ist aber, welcher Prozess sie aufruft. Dessen Thread wird dann zum ausführenden Thread der Methode. (Normalerweise, also wenn Sie nicht aus einem Xojo-Thread heraus agieren, ist dies der Main Thread – der einzige, der die GUI verändern darf. Und den Sie nicht extra definieren müssen: Alle Befehle in Xojo laufen normalerweise auf dem Main Thread, es sei denn, sie werden in einen eigenständigen Thread ausgelagert.)

So wie hier also wird die Methode Draw vom Ant-Thread aus gestartet, und das soll nicht sein. Lässt sich aber einfach beheben:

  • Klicken Sie auf das Bleistiftsymbol oben im Debugger, um den Code im Code-Editor angezeigt bekommen.
  • Ersetzen Sie die Zeile „Draw“ durch
xojo.core.timer.CallLater 0, AddressOf Draw

Ein Timer-Ationevent (und Xojo.Core.Timer.Calllater ist eine prima Abkürzung zu einem solchen, ohne einen Timer deklarieren zu müssen) findet immer auf dem Hauptthread der Anwendung statt und kann damit absturzfrei auf die GUI zugreifen. Probieren Sie’s mal aus! Die Ameise sollte jetzt loskrabbeln.

Wenn Sie ungeduldig sind: Hier ist das modernisierte Beispielprojekt (mit sämtlicher Anerkennung an Roger Meier), noch um Skalierbarkeit des Fensters (allerdings müssen Sie die Ameise dann neu losschicken) und einen Sync-Knopf ergänzt, der jede einzelne Bewegung der Ameise erkennbar werden lässt.

Update:

Das Beispielprojekt wurde noch ein bisschen beschleunigt durch Verwendung von Picture.RGBSurface statt Picture.Graphics, und Besitzer hochauflösender Monitore sehen jetzt sehr, sehr kleine Ameisen.

Hier geht es weiter mit Teil II über Anpassungen von Declares.

 

 

 

2 Gedanken zu “Alte Projekte modernisieren – Teil I

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