Zum Hauptinhalt springen

Verarbeitungshinweise zum SOA-Service

Verarbeitungshinweise zum SOA-Service Hinweise zur Verarbeitung innerhalb des SOA-Service

Der SOA-Service kann für unterschiedliche Zwecke verwendet werden. Im Folgenden wird an Beispielen erläutert, wie Funktionen des SOA-Service für bestimmte Anwendungsfälle programmiert werden können.

Task mit Betriebsart TIME

Beim Einsatz des SOA-Service in der Betriebsart TIME ist zunächst zwischen kurz laufenden Ereignisprozeduren (im Bereich bis 10 Sekunden) und länger laufenden Verarbeitungen zu unterscheiden. Bei längeren Verarbeitungen muss die Möglichkeiten bestehen, den Task und somit die laufende Ereignisprozedur innerhalb von wenigen Sekunden zu beenden. Dies geschieht beispielsweise beim Beenden des Systemdienstes oder beim Herunterfahren des Rechners. Die Ereignisprozedur muss daher den Zustand der Eigenschaft StopRequest regelmäßig überprüfen und sich im Falle eines angeforderten Stopps auch beenden.

Es ist zu beachten, das die gesetzte Ausführungszeit für das nächste Ereignis (SvcCallDelay oder SvcCallTime) nicht garantiert ist. Bei einem Neustart des Tasks wird das nächste Ereignis unmittelbar nach dem Start des Task ausgelöst und damit meist früher als vorgesehen. Wird der Rechner beispielsweise zu Wartungszwecken komplett heruntergefahren, findet das nächste Ereignis erst nach dem erneuten Systemstart statt und daher oft später als vorgesehen. Zur Kontrolle dieser Zustände kann der Zeitpunkt des letzten Ereignisses durch die Prozedur in der Datenbank gespeichert werden.

Beispiel:

sub SvcTime1
(
aObjHdl : handle; // Task-Objekt
aEvtType : int; // null
)

local
{
tCTS : caltime; // Kalenderzeit
}

{
// aktuelle Uhrzeit
tCTS->vmSystemTime();

// zu jeder vollen Stunde Verarbeitung starten
if (tCTS->vpMinutes = 0)
{
...
}

// in einer Stunde oder zur nächsten vollen Stunde wieder aufrufen
tCTS->vpMinutes # 0;
tCTS->vpSeconds # 0;
tCTS->vmSecondsModify(3600); // +1h

// Setzen der nächsten Ausführungszeit
aObjHdl->spSvcCallTime # CnvBC(tCTS);
}
Task mit Betriebsart Socket

Die einfachste Verarbeitungsform eines SOCKET-Services besteht aus vier Schritten:

  1. Verbindungsaufbau durch die Gegenstelle
  2. Empfangen einer Anfrage der Gegenstelle
  3. Senden einer Antwort an die Gegenstelle
  4. Verbindungsabbau durch den Service

Für das Empfangen der Anfrage und das Senden der Antwort ist das verwendete Kommunikationsprotokoll entscheidend. Neben Standardprotokollen wie HTTP, SMTP, POP3 oder Telnet können auch eigene Protokolle verwendet werden. Textbasierte Protokolle wie Telnet haben den Vorteil, das Anfragen und Antworten ganz oder teilweise in Textzeilen organisiert sind, die sehr einfach mit SckRead (..., _SckLine , ...) gelesen beziehungsweise mit SckWrite (..., _SckLine , ...) geschrieben werden können. Bei der Verwendung des HTTP kann das Objekt HTTP verwendet werden. Bei binären Protokollen muss dagegegen mit vordefinierten oder datenbezogenen Bytelängen gearbeitet werden.

Die Verarbeitungsschritte 2 und 3 können sich je nach Protokoll wiederholen. Die gesamte Sitzung wird dabei in einem einzigen Ereignis durchgeführt. Dabei sollte der Socket-Timeout für SckRead () und SckWrite () nicht zu niedrig eingestellt sein, da es ansonsten zu dem Fehler _ErrTimeout kommen kann, wenn die Gegenseite nicht schnell genug ist oder Kommunikationsverzögerungen eintreten.

Analog zu TIME-Services müssen Ereignisprozeduren, die längere Zeit laufen, den Zustand der Eigenschaft StopRequest regelmäßig überprüfen und sich im Falle eines angeforderten Stopps auch beenden.

Beispiel:

sub SvcSocket1
(
aObjHdl : handle; // Task-Objekt
aEvtType : int; // Ereignistyp
)

local
{
tSck : handle; // Socket Deskriptor
tPeer : alpha; // Gegenstelle
tRequest : alpha; // Anfrage
tReply : alpha; // Antwort
}

{
// Socket Deskriptor ermitteln
tSck # aObjHdl->spSvcSckHandle;

// IP-Adresse der Gegenstelle ermitteln
tPeer # tSck->SckInfo(_SckAddrPeer);

// Setzen des Timeouts auf 10 Sekunden
tSck->SckInfo(_SckTimeout, 10000);

try
{
while (aObjHdl->spStopRequest = false)
{
// Anfragezeile lesen
tSck->SckRead(_SckLine, tRequest);

// Verarbeitung beenden
if (tRequest = 'QUIT')
break;

...

// Antwortzeile senden
tSck->SckWrite(_SckLine, tReply);
}
}

// Timeout ist aufgetreten
if (ErrGet() = _ErrTimeout)
...

tSck->JobClose(); // keine weiteren Ereignisse
}
Task mit Betriebsart SOCKET mit Keep-Alive

Keep-Alives ermöglichen die Verwendung einer Socket-Verbindung über einen längeren Zeitraum, ohne das während der gesamten Verbindungsdauer eine Prozedur läuft und somit ein Datenbankbenutzer benötigt wird. Bei Verwendung des HTTP-Protokolls kann die Steuerung von Keep-Alives am einfachsten durch die Verwendung von HTTP -Objekten erfolgen. Bei anderen Protokollen müssen die Ereignisprozeduren dies selbst steuern.

Die Keep-Alive-Zeit bestimmt, für welche maximale Zeitspanne die Socket-Verbindung nach dem Ende der Ereignisprozedur noch offen bleibt, um weitere Daten der Gegenstelle empfangen zu können. In der Konfigurationsdatei des Tasks kann mit der Einstellung socket_keepalive ein Standardwert für diese Zeitspanne eingestellt werden, die Verwendung von Keep-Alives wird jedoch immer durch die Ereignisprozedur gesteuert.

Am Anfang einer Verbindung steht das Ereignis _SckEvtConnect. In der Ereignisprozedur gibt es jetzt drei Möglichkeiten, die Verbindung zu behandeln:

  1. Mit SckClose () wird die Verbindung getrennt und es folgen keine weiteren Ereignisse für die Verbindung.
  2. Mit SckInfo (<handle>, _SckKeepAlive , <time>) wird eine Keep-Alive-Zeit eingestellt. Die Verbindung bleibt bestehen und das nächste Ereignis ist entweder _SckEvtData, _SckEvtTimeout oder _SckEvtDisconnect. Die Keep-Alive-Zeit gilt nur bis zum nächsten Ereignis, ein weiteres Keep-Alive muss erneut eingestellt werden. Die Keep-Alive-Zeitangabe erfolgt in Millisekunden, alternativ kann die Konstante _SckDefaultKeepAlive verwendet werden, wodurch die für den Task voreingestellte Zeitspanne aus der Konfigurationsdatei verwendet wird.
  3. Wenn weder die erste noch die zweite Variante benutzt wird, erfolgt als nächstes Ereignis _SckEvtDisconnect für diese Verbindung.

Sofern ein Keep-Alive gesetzt wird und innerhalb der Zeit Daten von der Gegenstelle eintreffen, wird das Ereignis _SckEvtData ausgelöst. In diesem Ereignis können dann weitere Daten empfangen und verarbeitet werden und gegebenenfalls auch Daten zurückgesendet werden. In der Ereignisprozedur gibt es dieselben oben genannten drei Möglichkeiten zur weiteren Steuerung der Verbindung.

Treffen innerhalb der Keep-Alive-Zeit keine Daten der Gegenstelle ein, erfolgt das Ereignis _SckEvtTimeout. Die Verbindung ist nach wie vor offen, in diesem Ereignis könnten auch Daten an die Gegenstelle gesendet werden. Die Prozedur muss an dieser Stelle entscheiden, ob die Verbindung weiter bestehen bleibt. In diesem Fall muss ein erneutes Keep-Alive gesetzt werden. Andernfalls kann ein SckClose () erfolgen (kein weiteres Ereignis) oder die Prozedur beendet sich (_SckEvtDisconnect erfolgt).

Das Ereignis _SckEvtDisconnect wird ausgelöst, wenn in den Ereignissen _SckEvtConnect, _SckEvtData oder _SckEvtTimeout weder ein Keep-Alive gesetzt wird noch ein SckClose () erfolgt. Außerdem erfolgt der Aufruf, wenn innerhalb einer Keep-Alive-Zeit die Gegenstelle die Verbindung beendet.

Beispiel:

sub SvcWork
(
aSck : handle; // Socket Deskriptor
)
: logic;

local
{
tRequest : alpha; // Anfrage
tReply : alpha; // Antwort
}

{
try
{
// Anfragezeile lesen
aSck->SckRead(_SckLine, tRequest);
if (tRequest = 'QUIT')
return(false);

...

// Antwortzeile senden
aSck->SckWrite(_SckLine, tReply);
}

return(true);
}

sub SvcSocket2
(
aObjHdl : handle; // Task-Objekt
aEvtType : int; // Ereignistyp
)

local
{
tSck : handle; // Socket Deskriptor
}

{
// Socket Deskriptor ermitteln
tSck # aObjHdl->spSvcSckHandle;

// Setzen des Timeouts auf 10 Sekunden
tSck->SckInfo(_SckTimeout, 10000);

try
{
switch (aEvtType)
{
// Anfragedaten vorhanden
case _SckEvtConnect, _SckEvtData :
{
if (SvcWork(tSck))
// Setzen des Keep-Alives auf 30 Sekunden
tSck->SckInfo(_SckKeepAlive, 30000);
}

// Keine Daten innerhalb von 30 Sekunden
case _SckEvtTimeout :
{
tSck->SckClose();
}

// Verbindung beendet
case _SckEvtDisconnect :
{
...
}
}
}
}
Task mit Betriebsart SOCKET mit Keep-Alive und HTTP

Um einen Socket-Service mit dem HTTP-Protokoll zu realisieren, ist die Verwendung des HTTP -Objekts die einfachste Möglichkeit. Dabei übernehmen die HTTP-Objekte für das Empfangen der Anfrage und das Senden der Antwort auch die Steuerung des Keep-Alives. Bei Verwendung von HTTP/1.0 wird generell ohne Keep-Alive gearbeitet. Bei HTTP/1.1 bestimmt die Anfrage der Gegenstelle und die Antwort des Socket-Service ob Keep-Alive verwendet wird. Enthält die Anfrage den HTTP-Kopfeintrag Connection: close wird kein Keep-Alive verwendet. Wird beim Senden der Antwort bei HttpClose ( _HttpCloseConnection ) benutzt, wird ebenfalls kein Keep-Alive aktiviert.

Die Verbindung bleibt nach der Ereignisprozedur also nur unter folgenden Bedingungen bestehen:

  1. Es wird für das Empfangen der Anfrage und das Senden der Antwort jeweils ein entsprechendes HTTP -Objekt verwendet.
  2. Sowohl die Anfrage als auch die Antwort verwenden HTTP/1.1.
  3. Die Anfrage enthält kein "Connection: close" im HTTP-Header.
  4. Die Antwort wird ohne die Option _HttpCloseConnection gesendet.

Beispiel (verkürzt):

sub SvcSocket3
(
aObjHdl : handle; // Task-Objekt
aEvtType : int; // Ereignistyp
)

local
{
tSck : handle; // Socket Deskriptor
tRequest : handle; // HTTP Anfrage
tReply : handle; // HTTP Antwort
tReplyData : handle; // Antwortdaten (MemObj)
tStatus : alpha; // Status
}

{
// socket Deskriptor ermitteln
tSck # aObjHdl->spSvcSckHandle;

try
{
switch (aEvtType)
{
// Anfragedaten vorhanden
case _SckEvtConnect, _SckEvtData :
{
// Anfrage lesen
tRequest # HttpOpen(_HttpRecvRequest, tSck);
ProcessRequest(tRequest, var tStatus, var tReplyData);
tRequest->HttpClose(0);

// Antwort senden
tReply # HttpOpen(_HttpSendResponse, tSck);
tReply->spStatusCode # tStatus;

if (tReplyData > 0)
tReply->HttpClose(0, tReplyData);
else
tReply->HttpClose(_HttpCloseConnection);
}

// Keine Daten innerhalb von 30 Sekunden
case _SckEvtTimeout :
{
tSck->SckClose();
}

// Verbindung beendet
case _SckEvtDisconnect :
{
...
}
}
}
}
Task mit Betriebsart SOCKET mit Sessions

Sowohl bei der Verwendung von Keep-Alives als auch bei mehreren Verbindungen von einer Gegenstelle aus besteht oft die Notwendigkeit, die Kommunikation als eine zusammenhängende Sitzung (Session) zu verarbeiten. Dazu muss bei mehreren aufeinanderfolgenden Anfragen die Gegenstelle identifiziert werden. Da nach jedem Aufruf einer Ereignisprozedur eventuelle globale Daten und Feldinhalte verlorengehen, können Sitzungsdaten entweder mit der Anweisung SvcSessionControl (), in der Datenbank oder als zentrale Datenobjekte abgelegt werden.

Sofern die Verbindung durch den Einsatz von Keep-Alives bestehen bleibt, kann die Gegenstelle zuverlässig anhand von Quell-IP-Adresse (SckInfo (..., _SckAddrPeer )) und Port (SckInfo (..., _SckPortPeer )) bestimmt werden.

Da bei mehreren separaten Verbindungen der Quell-Port wechselt, muss in diesen Fällen eine Identifikation über das Protokoll erfolgen. Bei HTTP ist dies zum Beispiel relativ einfach durch die Verwendung von Cookies möglich. In dem Cookie kann die Session-ID, die von der Anweisung SvcSessionControl () zurückgegeben wurde, übertragen werden. Es bestehen aber auch die Möglichkeiten die Id in der URI oder als Parameter des Requests zu übergeben. Die Kommunikation über das HTTP wird durch das Objekt HTTP unterstützt. In anderen Fällen ist je nach verwendetem Protokoll die Verarbeitung von Sitzungen aufwändiger oder gar nicht möglich.

Da Sitzungen ohne eine Information von der Gegenstelle abgebrochen werden können, muss es für die temporären Sitzungsdaten eine Funktion zum Aufräumen abgelaufener Daten geben, die beispielsweise über einen TIME-Service realisiert werden kann und die abgelaufene Daten anhand eines Zeitstempels identifizieren kann.

Jobs

Der primäre Zweck von Jobs ist der Verarbeitung von Aufgaben in Zusammenhang mit einem Ereignis, aber außerhalb der Ereignisprozedur selbst. Beim TIME-Service kann dies der Fall sein, wenn beispielsweise die Ereignisprozedur in Sekundenabständen aufgerufen wird und sie lediglich die Verarbeitung von Aufgaben initiiert, sie aber nicht selbst durchführt. Beim SOCKET-Service dauert die Verarbeitung unter Umständen länger, wodurch die Gegenstelle eventuell einen Timeout-Fehler bekommt, bevor die Antwort gesendet werden kann. In diesem Fall könnten temporäre Antworten("In Bearbeitung") gesendet werden, während die eigentliche Verarbeitung ein Job erledigt. Dies ist oft einfacher realisierbar als die Kommunikation und die Verarbeitung in nur einer Prozedur zu implementieren.

Der einfachste Fall bei Jobs ist "Start and forget it". Der Starter überprüft lediglich, ob der Start erfolgreich ist, das Resultat und der Verarbeitungsverlauf sind nicht von Interesse. Das oben genannte Beispiel für den TIME-Service könnte wie folgt aussehen.

Beispiel:

sub JobWork
(
aObjHdl : handle; // Task-Objekt
aEvtType : int; // Ereignistyp
)

local
{
tJobData : alpha(1024); // Jobdaten
}

{
tJobData # aObjHdl->spJobData;
...
}

sub SvcTime2
(
aObjHdl : handle; // Task-Objekt
aEvtType : int; // Ereignistyp
)

local
{
tCTS : caltime;
tJobData : alpha(1024);
tJobID : int;
}

{
// neuer Auftrag in der Datenbank
if (RecRead(tblJtkJobTasks, 1, _RecFirst) = _rOk)
{
// Daten lesen
tJobData # faJtkTaskData;

// Job starten
tJobID # JobStart(_JobThread, 0, 'Service:JobWork', tJobData);

// Eintrag löschen
if (tJobID > 0)
RecDelete(tblJtkJobTasks, 0);
else
{
// Fehler protokollieren
SvcLog(_LogError, false, 'Fehler bei Jobstart: ' + CnvAI(tJobID));

// Setzen der nächsten Ausführungszeit in 5 Minuten
tCTS->vmSystemTime();
tCTS->vmSecondsModify(300); // +5m
aObjHdl->spSvcCallTime # CnvBC(tCTS);
}
}
}

Falls der Starter den Verarbeitungszustand und das Jobende erfahren will, muss mit JobOpen () ein Kontroll-Objekt angelegt werden, welches am Ende mit JobClose () auch wieder entfernt wird. Die Eigenschaft JobErrorCode ist 0 (_ErrOk), solange der Job läuft und _ErrTerminated, wenn der Job korrekt beendet wurde. Da in der Jobverarbeitung aber auch benutzerdefinierte Fehler enstehen können, kann dies über die Eigenschaft JobStatus signalisiert werden.

Beispiel (Ausschnitt):

tJobCtrl # JobOpen(tJobID);
if (tJobCtrl)
{
// Schleife läuft, solange der Job läuft
while (tJobCtrl->spJobErrorCode = 0)
{
if (tJobCtrl->spJobStatus > 0)
...

SysSleep(500);
}

tJobCtrl->JobClose();
}

In bestimmten Fällen wartet die Ereignisprozedur aber gar nicht das Ende der Jobverarbeitung ab, sondern beendet sich vorher. Die Gegenstelle sendet dann beispielsweise erneut eine Anfrage, um das Resultat des Jobs abzuholen. In diesem Fall kann die Ereignisprozedur erneut ein JobOpen () auf den Job durchführen, sofern ihr der benötigte Job-Deskriptor bekannt ist (diesen kann die Gegenstelle beispielsweise im Cookie mitsenden). Für ein solches Verfahren ist es notwendig, dass auch nach dem Ende der Jobprozedur der Job-Deskriptor gültig ist und ein JobOpen () funktioniert. Zu diesem Zweck wird beim Start des Jobs eine Zeitspanne angegeben, in der der Job-Deskriptor nach dem Ende der Jobprozedur noch für die Anweisung verwendet werden kann:

// Job starten mit 5 Minuten ID-Gültigkeit nach Jobende
tJobID # JobStart(_JobThread, 300, 'Service:JobWork', tJobData);

Wenn beim Ende der Jobprozedur bereits ein Kontrollobjekt vorhanden ist, bleibt dies natürlich bis zum JobClose () vorhanden.

Jobs mit Verarbeitungsschleifen

In vielen Anwendungen endet der Job nach erfolgreicher Erledigung der Aufgabe. Unter Umständen soll die Jobprozedur noch weitere Aufgaben übernehmen. Der Verarbeitung weiterer Aufgaben kann in Form einer Schleife realisiert werden, die entweder auf Daten der Kontrollprozedur wartet (siehe Kommunikation mit Jobs ) oder in einen Wartezustand geht. Mit dem Befehl JobSleep () kann die Jobprozedur in einen Wartezustand versetzt werden, der im Gegensatz zu SysSleep () vor Ablauf der Zeit enden kann. Führt ein Kontrollobjekt mit der Funktion JobControl () eine der Optionen _JobWakeup, _JobStop oder _JobTerminate durch, wird JobSleep () abgebrochen und die Jobprozedur kann entsprechend reagieren.

Beispiel (Ausschnitt aus der JobProzedur):

while (aObjHdl->spStopRequest = false)
{
// Verarbeitung
...

// auf ein Ereignis warten
while (JobSleep(60000) = false) {}
}
Kommunikation mit Jobs

Jobprozeduren können mit dem Kontroll-Objekt über MSX-Befehle bidirektional Daten austauschen. Dazu werden auf beiden Seiten jeweils zwei Kommunikationskanäle zum Senden und Empfangen angelegt. Findet die Kommunikation nur in eine Richtung statt, benötigt jede Seite auch nur einen Kanal.

Sendet eine der beiden Seite eine Nachricht, muss diese zunächst komplett sein, bevor sie für die andere Seite sichtbar wird. Es kann also noch nicht mit dem Lesen einer Nachricht begonnen werden, falls auf der anderen Seite noch kein MsxWrite ( _MsxEnd , 0 ) erfolgt ist. Die fertige Nachricht wird zunächst im Hauptspeicher vorgehalten und die Eigenschaft JobMsxReadQ auf der Gegenseite um eins erhöht. Anschließend kann die andere Seite die Nachricht öffnen und den Inhalt auslesen. Nach dem Öffnen der Nachricht reduziert sich JobMsxReadQ wieder um eins.

Auf der Seite des Senders wird bei der Ausführung als Thread die Eigenschaft JobMsxWriteQ um eins erhöht, solange die Nachricht noch nicht von der anderen Seite geöffnet wurde. Bei der Ausführung als Prozess werden geschriebene Nachrichten immer sofort zum Zielprozess übertragen und dort zwischengespeichert. Daher ist die Eigenschaft JobMsxWriteQ bei der Verwendung von Jobprozessen immer 0.

Sind beim Beenden der Jobprozedur noch Nachrichten vorhanden, können diese alle noch von der Kontrollprozedur gelesen werden. Umgekehrt bleiben beim Beenden der Kontrollprozedur noch alle Nachrichten für die Jobprozedur erhalten. Erst beim Schließen des Kommunikationskanals auf beiden Seiten werden ungelesene Nachrichten gelöscht.

Für MsxRead () kann jede Seite einen eigenen Timeout über den Befehl JobControl (..., _JobMsxTimeoutRead , ...) setzen. Höhere Timeout-Werte stellen kein Problem dar, da beim Stoppen des Tasks oder dem Stoppen des Jobs die Funktionen MsxRead () oder MsxWrite () abgebrochen werden und der Fehler _ErrTerminated zurückgeliefert wird. Die gilt allerdings nicht für MsxRead ()- oder MsxWrite ()-Operationen auf Sockets oder Dateien.

Beispiel:

sub JobWork
(
aObjHdl : handle; // Task-Objekt
aEvtType : int; // Ereignistyp
)

local
{
tError : int;

tMsxHdlR : handle;
tMsxHdlW : handle;

tMsgID : int;
tMsgItem : int;
tMsgText : alpha(100);
}

{
// bi-direktionale Kommunikation öffnen
tMsxHdlR # MsxOpen(_MsxThread | _MsxRead, aObjHdl);
tMsxHdlW # MsxOpen(_MsxThread | _MsxWrite, aObjHdl);

// Timeout auf 5 Sekunden setzen
aObjHdl->JobControl(_JobMsxTimeoutRead, 5000);

// Verabeitungsschleife
while (!aObjHdl->spStopRequest)
{
// nächste Nachricht lesen
tError # tMsxHdlR->MsxRead(_MsxMessage, tMsgID);

// Nachricht empfangen
if (tError = _rOk)
{
tMsxHdlR->MsxRead(_MsxItem, tMsgItem);
tMsxHdlR->MsxRead(_MsxData, tMsgText);
tMsxHdlR->MsxRead(_MsxEnd, tMsgID);

...

// Antwort senden
tMsxHdlW->MsxWrite(_MsxMessage, tMsgID);
tMsxHdlW->MsxWrite(_MsxItem, 55);
tMsxHdlW->MsxWrite(_MsxData, ...);
tMsxHdlW->MsxWrite(_MsxEnd, 0);
}

// Timeout-Fehler
else if (tError = _ErrTimeout)
...
DbgTrace('MsxRead timeout...');

// sonstiger Fehler bei MsxRead
else
...
}

tMsxHdlR->MsxClose();
tMsxHdlW->MsxClose();
}

sub SvcJob1
(
aObjHdl : handle; // Task-Objekt
aEvtType : int; // Ereignistyp
)

local
{
tJobData : alpha(1024);
tJobID : int;
tJobCtrl : handle;

tMsx : handle;
tMsgID : int;
tMsgItem : int;
tMsgText : alpha(100);
}

{
...

try
{
// Job starten
tJobID # JobStart(_JobThread, 0, 'Service:JobWork');
tJobCtrl # JobOpen(tJobID);

// Timeout auf 3 Minuten setzen
JobControl(tJobCtrl, _JobMsxTimeoutRead, 180000);

// Daten senden
tMsx # MsxOpen(_MsxWrite | _MsxThread, tJobCtrl);
tMsx->MsxWrite(_MsxMessage, 1);
tMsx->MsxWrite(_MsxItem, 77);
tMsx->MsxWrite(_MsxData, tJobData);
tMsx->MsxWrite(_MsxEnd, 0);
tMsx->MsxClose();

// Antwort empfangen
tMsx # MsxOpen(_MsxRead | _MsxThread, tJobCtrl);
tMsx->MsxRead(_MsxMessage, tMsgID);
tMsx->MsxRead(_MsxItem, tMsgItem);
tMsx->MsxRead(_MsxData, tMsgText);
tMsx->MsxRead(_MsxEnd, tMsgID);

...
}

if (tJobCtrl > 0)
tJobCtrl->JobClose();
if (tMsx > 0)
tMsx->MsxClose();
}
Jobmodus (Threads vs. Prozesse)

Jobs können entweder als eigener Thread im laufenden Taskprozess oder als separater Betriebssystemprozess gestartet werden.

Im Thread-Modus kann die Job-Prozedur sehr schnell gestartet werden, da beispielsweise die Datenstruktur im Speicher kopiert werden kann und nicht erneut aus der Datenbank geladen werden muss. Die Kommunikation mit dem Kontroll-Objekt kann direkt und ohne nennenswerten Overhead erfolgen. Der zusätzliche Thread benötigt nur geringe Resourcen und wird garantiert zusammen mit dem Taskprozess beendet.

Im Prozess-Modus läuft hingegen der Job unabhängig vom Taskprozess. Ein Absturz des Jobprozesses führt somit nicht zum Beenden des Taskprozesses. Dies kann bei Verarbeitungen mit DLLs oder mit COM (Component Object Model) von Vorteil sein. Außerdem verfügt der Jobprozess über einen vollständig eigenen Adressraum, der nicht mit anderen Jobs oder dem Taskprozess geteilt werden muss. Der Start eines Jobs im Prozess-Modus dauert etwas länger als im Thread-Modus und der Job verbraucht auch mehr Resourcen des Betriebssystems. Beispielsweise sind für die Kommunikation mit dem Taskprozess zwei zusätzliche Threads erforderlich.