Programmierung

Thread-Management im CONZEPT 16-Windows-Client

Der CONZEPT 16-Entwickler muss sich bei der Implementation seiner Anwendung nicht um die Thread-Verarbeitung für das GUI oder die Ausführung der Prozeduren und Ereignisse kümmern. Die Laufzeitumgebung der VSO (vectorsoft objects library) erledigt dies automatisch. Manchmal führt ein "Blick unter die Motorhaube" jedoch zu einem besseren Verständnis der Abläufe…


Prozesse und Threads unter Windows

Wenn Windows ein Programm startet, erzeugt das Betriebssystem hierfür einen Prozess. Dieser unterteilt sich in sogenannte Threads, die parallel ausgeführt werden. Zunächst enthält der Prozess nur den Haupt-Thread. Der Haupt-Thread wiederum ist in der Lage weitere Threads zu starten. Entwickler verwenden Threads um die Parallelität eines Programmes zu erhöhen.

Thread-Management im Windows-Client

Der vollgraphische Client besteht zu einem großen Teil aus der dynamischen Linkbibliothek (DLL) c16_objw.dll. Diese enthält u.a. alle graphischen Oberflächen- und Druckobjekte (genannt VSO – vectorsoft objects library).

Die VSO besitzt ein eigenes Thread-Management zur Verbesserung der Bedienbarkeit von CONZEPT 16 Anwendungen ohne zusätzlichen Aufwand für den CONZEPT 16-Entwickler.

Anwendungen unter Windows mit graphischer Benutzeroberfläche sind ereignisgesteuert. Windows sendet einem Prozess für alle relevanten Ereignisse, die auftreten können, sogenannte Nachrichten (Messages). Wird beispielsweise eine Schaltfläche ausgelöst, sendet Windows dem Thread, dem die Schaltfläche gehört, eine entsprechende Nachricht. Besteht der Prozess nur aus dem Haupt-Thread, bearbeitet dieser die Nachricht.

Das folgende Beispiel demonstriert ein Ereignis, das ausgeführt werden soll, wenn eine Schaltfläche betätigt wird.

sub EvtClicked
(
  aEvt : event;
)
: logic;

  local
  {
    tStart : int;
  }

{
  tStart # SysTics();

  while (SysTics() - tStart <= 10000)
  {
    // do something
    SysSleep(10);
  }

  return(true);
}

Die Schleife beendet sich nach 10 Sekunden. Da der Haupt-Thread den Prozedurcode abarbeitet, kann dieser während der 10 Sekunden keine Nachrichten abfragen. Für den Anwender äußert sich dies in einem „hängenden“ Fenster, das nicht verschoben werden kann und einer entsprechenden Meldung von Windows im Caption desselbigen. Versucht der Anwender nun das Fenster über die Schließen-Schaltfläche in der Titelleiste zu beenden, teilt Windows ihm mit, das die Anwendung nicht mehr reagiert. An dieser Stelle kommt das Thread-Management von CONZEPT 16 zum Einsatz.

GUI- und PCODE-Thread

Damit der CONZEPT 16-Anwender mit der Benutzeroberfläche seiner Anwendung interagieren kann, auch wenn eine längere Prozedur ausgeführt wird, startet die VSO zwei Threads: einen, der die Benutzerinteraktion steuert (GUI-Thread) und einen weiteren, der die Prozedurausführung (PCODE-Thread) übernimmt.
Der GUI-Thread wartet auf Windows-Nachrichten. Drückt der Anwender wieder eine Schaltfläche nimmt der GUI-Thread die Nachricht entgegen, ermittelt die Ereignisfunktion EvtClicked und teilt dem PCODE-Thread mit, dass eine Prozedur ausgeführt werden soll. Der PCODE-Thread übernimmt ab dann die Durchführung der EvtClicked-Funktion.
Der GUI-Thread ist ab Zeitpunkt der Übergabe bereit für die Bearbeitung neu eintreffender Windows-Nachrichten. Er kann nun z.B. auf das Verschieben des Fensters durch den Benutzer reagieren, obwohl gerade die EvtClicked-Funktion durchgeführt wird.

Thread-Umschaltung

Die dargestellte EvtClicked-Funktion wird vom PCODE-Thread durchgeführt, und zwar von Anfang bis Ende ohne Unterbrechung. Während dieser Zeit kann der GUI-Thread dem PCODE-Thread keine weitere Ereignisfunktion übergeben, da dieser schon eine Prozedur abarbeitet.

Der PCODE-Thread hat jedoch die Möglichkeit, dem GUI-Thread die Kontrolle für eine gewisse Zeit zurückzugeben. Die modifizierte Schleife des vorherigen Beispiels zeigt wie:

while (SysTics() - tStart <= 10000)
{
  …
  WinSleep(0);
  …
}

Der Befehl WinSleep() unterbricht die Prozedur-Ausführung für die angegebene Zeitspanne. Der GUI-Thread hat jetzt wieder die Möglichkeit ein weiteres Ereignis auszuführen, das in der Zwischenzeit eingetroffen ist. Ist die Zeitspanne abgelaufen oder keine Windows-Nachricht vorhanden kehrt der Befehl zurück und setzt die Prozedurausführung fort.

Der Unterschied zwischen SysSleep() und WinSleep() ist, dass letzterer Befehl die Windows-Nachrichten-Schleife abfragt und neu eingetroffene Ereignisse bearbeitet.

Prozedurbefehle und Eigenschaften

Neben WinSleep() existiert eine weitere Konstellation währenddessen der Prozedur-Thread die Kontrolle an den GUI-Thread zurückgibt. Diese tritt immer ein, wenn ein VSO-Eigenschaft oder ein VSO-Befehl durchgeführt wird.

while (SysTics() - tStart <= 10000)
{
  …
  $:Progress->wpProgressPos # tNewProgressPos;
  …
}

Im Beispiel wird die Position eines Progress-Bar gesetzt, um den Fortgang der Prozedurdurchführung anzuzeigen. Das Setzen der Eigenschaft bedingt die Umschaltung in den GUI-Thread. Bei dieser Gelegenheit überprüft dieser auch gleich, ob neue Windows-Nachrichten vorliegen. Dies führt gegebenenfalls auch wieder dazu, dass während des Aufrufs der Eigenschaft CONZEPT 16-Ereignisse durchgeführt werden.
Nach der Rückkehr des Aufrufs von wpProgressPos setzt der PCODE-Thread die Prozedur nach der Eigenschaft fort.
Diese Thread-Umschaltung wird für Prozedur-Befehle und Eigenschaften durchgeführt. Betroffen sind alle Win-, Prt– und Com-Befehle sowie alle Win-, Prt– und Com-Eigenschaften. Andere Befehle oder Eigenschaften, die unabhängig von der VSO sind, führen nicht zu einer Umschaltung (z.B. die _SysProp-Eigenschaften oder der Befehl RecRead()).

Asynchrone Verarbeitung

Um dem Anwender z.B. die Möglichkeit zu geben, einen länger andauernden Vorgang abzubrechen, bietet CONZEPT 16 die Möglichkeit Dialoge asynchron auszuführen.

tDialog->WinDialogRun(_WinDialogAsync);

Der Befehl kehrt nach dem Erstellen und Laden des Dialoges sofort zum PCODE-Thread zurück. Dadurch hat dieser die Möglichkeit die Prozedurverarbeitung fortzusetzen, was sonst bei einem modalen Dialogaufruf nicht möglich wäre.

while (tDialog->WinDialogResult() <> _WinIdCancel))
{
  // länger dauernde Verarbeitungsschritte
  SysSleep(10);
}

Die Schleife endet, wenn der Anwender die Schließen-Schaltfläche im Dialog drückt oder einen Cancel-Button betätigt. Zum Beenden des Dialoges ist also kein Ereignis notwendig, da der GUI-Thread während der Durchführung der Schleife den Status von WinDialogResult() aktualisiert, sobald eine entsprechende Schaltfläche betätigt wird.

Dies bedeutet jedoch nicht, dass asynchrone Dialoge keine Ereignisse auslösen können.
Die Prozedur muss dem GUI-Thread lediglich die Möglichkeit geben diese auch durchführen zu können.

Im folgenden Beispiel wird davon ausgegangen, dass es eine Schaltfläche gibt, die das Ereignis EvtClicked auslöst, wenn diese gedrückt wird.

sub EvtClicked
(
  aEvt                 : event;    // Ereignis
)
: logic;
{
  aEvt:obj->wpCustom # 'Cancel Clicked';
  return(true);
}

Der Button enthält nach dem durchführen der Funktion den Text "Cancel Clicked" in seiner Custom-Eigenschaft. Die folgende exemplarische Funktion reagiert auf diesen Umstand:

sub StartAsyncWork

  local
  {
    tDialog : handle;
  }

{
  tDialog # WinOpen('AsyncDialog',_WinOpenDialog);

  tDialog->WinDialogRun(_WinDialogAsync);

  with AsyncDialog
  {
    while ($:Cancel->wpCustom != 'Cancel Clicked')
      WinSleep(100);
  }

  tDialog->WinClose();
}

Können Sie aus dem Artikel Erkenntnisse für sich gewinnen, die Sie in Ihre Entwicklung einfließen lassen können?

Keine Kommentare

Kommentar abgeben