Session D-WERKNetzwerkprogrammierung nicht nur für NetzwerkeinsatzSebastian Flucke
|
Viele Aspekte sind dabei allerdings durchaus auch
für Single-User-Standalone-Applikationen interessant!
|
Keine Panik vor der "Multi"-Manie <s>!
Folgende "Multi"-Begriffe sind nicht Gegenstand dieser Session:
Dagegen werden die Begriffe Multi-User, Multi-Session / Multi-Tasking und Multi-Instancing im weiteren näher erläutert und untersetzt.
Multi-User-Applikationen sind Applikationen, die in einem Netzwerk betrieben werden und von mehreren Anwendern gleichzeitig genutzt werden können.
Bei der Programmierung solcher Applikationen sind die folgenden multi-user-spezifischen Probleme zu lösen:
Diese vier Problemkreise bestehen unabhängig davon, ob die betreffende Applikation file-server-orientiert oder client/server-orientiert programmiert ist (siehe dazu die Sessions der Sektionen C/S und TIER).
Alle aktuellen Windows-Betriebssysteme sind Multi-Tasking-Systeme, d.h. es können mehrere Tasks quasi parallel ablaufen, indem sie sich die Prozessorzeit in sehr kleinen Scheibchen ständig teilen.
Damit besteht allerdings auch die Möglichkeit, unter einem Windows-System die gleiche Applikation mehrfach als Sessions in verschiedenen Tasks zu starten - und schon hat man die Problemkreise (1) bis (4) auf dem Tisch, obwohl in dem konkreten PC vielleicht nicht einmal eine Netzwerk-Karte vorhanden ist.
Ergo: Die Multi-User-Probleme sind unter Windows auch gleichzeitig Multi-Tasking-Probleme.
Jede echte Windows-Applikation bietet neben der normalen Arbeit mit nicht-modalen Masken außerdem die Möglichkeit der Mehrfach-Instanzierung von Datenerfassungs-Masken (Forms).
Dadurch ergeben sich unbestreitbare Vorteile bezüglich der Benutzeroberfläche. Z.B. muß die Erfassung eines neuen Kunden nicht abgebrochen werden, nur um die Telefonnummer eines anderen Kunden vom System abfragen zu können. Man startet die Kundenmaske einfach ein zweites Mal und schon kann die gewünschte Auskunft erlangt werden.
Mit der Mehrfach-Instanzierung von Masken hat man allerdings schlagartig die Problemkreise (1), (4) und teilweise auch (3) schon innerhalb einer einzigen Session einer Applikation auf dem Tisch! Lediglich der Problemkreis (2) spielt bei Multi-Instancing keine Rolle, da es innerhalb einer einzelnen VFP-Session keine Parallel-Verarbeitung gibt.
Bei der Entscheidung, welche Art von Applikation man programmieren sollte, spielen folgende Dinge eine entscheidende Rolle:
...daß das "Multi" sein muß!
Die folgenden Empfehlungen verhindern unnötigen Aufwand, der aus der Pflege mehrerer Varianten resultieren würde, und bieten ein leicht nutzbares Erweiterungspotential.
Denn irgendwann kommt der Kunde ja doch und will mindestens eines der "Multis" mal eben schnell umgesetzt haben - und wenn man dann klug vorgebaut hat...:
Voraussetzung ist natürlich, den zweifellos höheren einmaligen Aufwand für die netzwerktaugliche Programmierung einmal umfassend in einem eigenen Framework erarbeitet oder mit einem fremden Framework eingekauft zu haben (zu Frameworks siehe die Sessions der Sektion FWK).
TIP: Immer multi-user-, multi-session- und multi-instancing-tauglich
programmieren. Irgendwann ereilt es einen doch!
|
Netzwerkfähigkeit durch multi-instanzierbare Masken
Die Multi-Instanzierbarkeit von Masken ist im wesentlichen für Dateneingabe- und Datenpräsentations-Masken von großer Bedeutung. Für ablaufsteuernde Dialoge dagegen spielt Multi-Instanzierbarkeit nur eine sehr geringe Rolle.
Für die Programmierung multi-instanzierbarer Masken sind folgende Aspekte relevant, die im weiteren näher erläutert werden:
Diese erste Voraussetzung ist fest in die Programmiersprache von Visual FoxPro integriert.
Jede mit dem Formdesigner erstellte Maske ist ein Objekt mit einem eigenen Namensraum für Property- und Methoden-Bezeichner. Dadurch kann es bei der Mehrfach-Instanzierung einer Maske keine gegenseitigen Beeinflussungen oder Überschneidungen beim Zugriff auf Methoden oder Properties geben!
Die Private DataSession einer Form beinhaltet mehrere wichtige Aspekte:
TIP: Eine vollständige Liste der datasession-abhängigen SET-Einstellungen findet man in der VFP-Hilfe unter dem Stichwort "SET DATASESSION"! |
TIP: In jeder neu eröffneten Private DataSession befinden sich diese Set-Einstellungen leider in der amerikanischen Grundeinstellung, deshalb muß man sich einen Mechanismus schaffen, der diese Einstellungen auf die selbst definierten Standards umstellt! |
Dateien immer im Modus SHARED öffnen. Die Notwendigkeit dieser Verfahrensweise ist leicht einzusehen:
Neben den schon angeführten Dingen gibt es noch einige weitere Aspekte, bei denen es zu Überschneidungen kommen kann, die eine Mehrfach-Instanzierung von Masken verhindern:
TIP: Vermeiden Sie konsequent die Benutzung globaler
Variablen, sie sind nur in ganz extremen Ausnahmen notwendig!
|
TIP: Benutzen Sie zum Erstellen temporärer Tabellen am besten Views, SELECT INTO CURSOR bzw. CREATE CURSOR. Für diese Dateien verwaltet Visual FoxPro selbst die temporären Dateinamen und löscht diese Dateien auch automatisch, wenn sie geschlossen werden (das gilt übrigens auch für Indizies, die man für einen View oder Cursor anlegt). |
TIP: Sollten dennoch eigene temporäre Dateien angelegt werden, benutzten Sie die Funktionen SYS(3) oder SYS(2015) zum Bilden von temporären Dateinamen. Vergessen Sie aber niemals die Prüfung, ob eine Datei mit dem entsprechenden Namen nicht doch schon existiert! |
Und außerdem müssen Sie selbst für das Löschen solcher Dateien sorgen!
TIP: Benutzen Sie auch für Menüs und Popups die Funktion SYS(2015) in Verbindung mit einer Prüfung, ob der Name nicht doch schon benutzt wird! |
Die Koordinierung existierender Instanzen von Masken wird weiter unten behandelt.
Die Multiuser-Fähigkeit einer Maske muß immer dann gegeben sein, wenn diese Maske zu einem Zeitpunkt mehrfach geöffnet sein kann:
Die wichtigste Aufgabe in der Multi-User-Programmierung besteht in der Koordinierung und Konfliktlösung bei quasi gleichzeitiger Bearbeitung von Datensätzen. Für diese Aufgabe stehen in Visual FoxPro verschiedene Lockingtypen zur Verfügung:
Bei pessimistischem Locking wird der entsprechende Datensatz gesperrt, sobald in der Maske die erste Veränderung vorgenommen wird:
Im optimistischen Locking sperrt Visual FoxPro den Datensatz nur kurzeitig in dem Moment, wenn der Satz gespeichert wird:
Für diesen Fall kann man mit den Befehlen und Funktionen GETNEXTMODIFIED(), GETFLDSTATE(), OLDVAL(), CURVAL(), TABLEREVERT() und TABLEUPDATE() sowie unter Einsatz von Transaktionen eine beliebig differenzierte automatische oder auch durch den Nutzer beeinflußbare Konfliktlösung programmieren.
Angesichts des Gleichgewichts in der Bewertung der beiden Locking-Verfahren gibt ein weiterer Aspekt den Ausschlag, nämlich die Verfahrensweise bei Views: Views können generell nur mit dem optimistischen Verfahren bearbeitet werden, da die meisten Backend-Datenbanken keine pessimistische Sperrung unterstützen.
TIP: Arbeiten Sie aus Gründen der Einheitlichkeit
in Programmierung und Bedienung immer mit optimistischem Locking!
|
Die Auswahl eines bestimmten Locking-Typs ist in Visual FoxPro immer mit der Entscheidung für einen der zwei möglichen Buffermodes verbunden:
Damit können durch optimistisches Table-Buffering alle relevanten Fälle gut abgedeckt werden:
TIP: Öffnen Sie alle Ihre Tabellen und Views
immer mit optimistischem Table-Buffering!
|
ACHTUNG! Der SELECT-SQL-Befehl ist der einzige Befehl, der nicht auf die Daten aus dem ggf. schon veränderten Puffer zurückgreift, sondern die Ergebnisse immer direkt aus den Datei-Inhalten auf der Festplatte ermittelt. Dieses Verhalten kann man gezielt ausnutzen, wenn man z.B. bei in einer Eingabe-Validierung nach Doubletten sucht, da der neu eingebene Wert nur im Puffer steht und damit in das Ergebnis eines entsprechenden SQL-Statements nicht mit einbezogen wird. |
ACHTUNG! Die mit entsprechendem Locking und Buffering festgelegte Verfahrensweise dient nicht nur zur Koordinierung zwischen mehreren Instanzen einer speziellen Maske.
Vielmehr ist dies ein tabellenbezogener Mechanismus, der auch Zugriffskonflikte bei Überschneidungen von verschiedenen Masken oder Programmteilen bezüglich einer Tabelle verabeiten können muß.
Voraussetzung dafür ist allerdings, daß die Zugriffsmechanismen an allen Stellen im Programm bzgl. einer Tabelle nach der gleichen Strategie vorgehen (siehe auch weiter unten).
Beim TableUpdate() können in der Konstellation "optimistic Table-Buffering" folgende Arten von Konflikten auftreten:
Diese Konflikte äußern sich in dem Rückgabewert .F. der TableUpdate()-Funktion und/oder dem Auslösen eines Fehlers, wobei die detaillierte Ursache in beiden Fällen aus dem mit AError() zu füllenden Error-Array ermittelt werden kann.
Updatekonflikte treten dann auf, wenn der bearbeitete Datensatz zwischenzeitlich durch jemand anderen geändert wurde:
Locking-Konflikte entstehen, wenn einer der zu speichernden Datensätze nicht gesperrt werden kann:
Ursache dafür kann darin liegen, daß der betreffende Satz in diesem Moment durch einen zeitgleichen Update-Prozeß einer anderen Arbeitsstation oder einer anderen Instanz der Applikation oder im Extremfall durch eine andere Maske der gleichen Instanz der Applikation gesperrt ist (wenn diese andere Maske mit pessimistischem Locking arbeiten sollte).
Rules- und Triggerfehler entstehen, wenn eine der zuständigen Rules oder Trigger feststellt, daß die betreffende Operation nicht ausgeführt werden darf:
TIP: Beachten Sie, daß die Field- und Record-Validierung nicht erst beim TableUpdate() verarbeitet werden, sondern unabhängig vom BufferMode immer bereits beim Verlassen des Feldes bzw. des Datensatzes. Beim TableUpdate() kann deshalb nur die Record-Validation fehlschlagen und auch nur, wenn der Satzzeiger noch nicht bewegt wurde! |
Sonstige Fehler resultieren aus der Verletzung von fest in Visual FoxPro integrierten Regeln:
TIP: Beachten Sie bitte, daß die in diesem Abschnitt beschriebenen Fehler-Konstellationen speziell im Zusammenhang mit optimistischem Table-Buffering beschrieben sind! |
Einige weitere Aspekte sind im nachfolgenden Abschnitt beschrieben, haben allerdings für die Multi-User-Fähigkeit von Masken ebenso Gültigkeit!
Die im vorigen Abschnitt "Multi-User-Fähigkeit von Masken" diskutierten Aspekte sind eigentlich nicht masken-spezifisch, sondern können ebenso auftreten, wenn die Inhalte von Datenfeldern und Datensätzen programmatisch durch REPLACE, INSERT und verwandte Befehle verändert wurden, und dabei wie empfohlen optimistisches Table-Buffering aktiv ist.
In den folgenden Punkten werden darüberhinausgehende Aspekte multi-user- und multi-session-fähiger Applikationen behandelt, die gleichermaßen auch für Masken relevant sind.
In bestimmten Fällen ist es wünschenswert, daß eine gewisse Menge von Datensätzen ungestört bearbeitet werden kann, z.B. alle Positionen einer Rechnung.
In diesem Fall muß man zu einer besonderen Strategie greifen, denn man könnte zwar alle existierenden Positionssätze speichern, aber dadurch nicht die Neuanlage von Rechnungspositionen in der betreffenden Rechnung verhindern.
Deshalb verwendet man die RLOCK()-Funktion für eine Satzsperre auf dem zugehörigen Satz in einer übergeordneten Datei (z.B. in der Rechnungskopf-Datei) als Semaphore, und der Sperrstatus gibt Auskunft darüber, ob die betreffende Rechnung überhaupt bearbeitet werden darf.
Wenn sich nun alle rechnungsbearbeitenden Programmteile (Masken und andere Programmsegmente) an diese Regelung halten, kann niemals der Fall eintreten, daß bestimmte Rechnungspositionen von zwei Stationen gleichzeitig verändert werden, d.h. es bedarf in diesem Fall keiner speziellen Locking-Vorkehrungen bzgl. der Rechnungspositionen.
Für die Konsistenz von Auswertungen kann es notwendig sein, daß während des Ablaufs einer Auswertung gewährleistet ist, daß keine Änderungen in den einbezogenen Daten erfolgen können: Eine Soll-Haben-Aufstellung würde z.B. verfälscht, wenn zeitlich gesehen zwischen der Ermittlung der Soll-Summe und der Ermittlung der Haben-Summe eine neue Buchung eingetragen würde, die dann zwar in der Haben- aber nicht in der Soll-Summe auftauchen würde.
Auch in diesem Fall kommt am günstigsten eine Sperr-Semaphore durch einen RLOCK()-Befehl auf einer hinreichend übergeordneten Tabelle als Lösung in Frage.
Auch Such- und Prüfprozesse müssen ggf. mit einer Locking-Semaphore vor einer zeitlichen Überschneidung geschützt werden.
Beispiele für derart zu schützende Prozess sind:
In solchen Fällen wird eine Satzsperre gesetzt, die entsprechende Prüfung oder Ermittlung durchgeführt und danach die Sperre wieder freigegeben, wodurch eine zeitliche Überschneidung dieser Aktivitäten verhindert wird, die zu Verfälschungen führen würde.
Wenn ein Record- oder File-Lock als Semaphore dafür verwendet wird, daß eine bestimmte Operation im Moment nicht ausgeführt werden darf, ist die Kennzeichnung völlig ausreichend.
Andererseits ist es aber sehr wünschenswert, dem Anwender der Arbeitsstation, die auf eine Freigabe warten muß, diverse Einzelheiten über den Grund und die Dauer der Sperrung mitzuteilen.
Deshalb sollte man Dateien, in denen Semaphoren hinterlegt werden, mit zusätzlichen Feldern versehen:
SPERRE_WER C 10 SPERRE_WANN T SPERRE_WARUM M oder C
Wird nun ein RLOCK() als Semaphore gesetzt, wird in diese Felder eingetragen, durch welchen Benutzer oder durch welche Arbeitsstation zu welchem Zeitpunkt und aus welchem Grund dieser Satz gesperrt ist.
Diese Informationen können dann im Falle einer fehlgeschlagenen Sperre den wartenden Nutzern in einer Maske angezeigt werden.
Bei Client-Server-Lösungen hat man auf Grund der Eigenschaften der Backend-Datenbank u.U. keine Möglichkeiten, eine explizite Satzsperre zu setzen, wie sie für Semaphoren notwendig wäre.
In diesem Fall kann man sich an Stelle von Locks mit TimeStamp-Semaphoren behelfen:
Die im TimeStamp hinterlegte Zeit ist also eher eine Art WatchDog-TimeOut, bei dessen Ablaufen eine noch gesetzte Semaphore für ungültig erklärt wird.
Die Verwendung von Sperrstrategien an verschiedenen Stellen einer Applikation erfordert einen konsistenten Einsatz der Sperrmechanismen.
Besondere Aufmerksamkeit verdient dabei die Benutzung übereinstimmender Ebenen des Semaphoren-Lockings.
Beispiel:
Dieses relativ einfache Beispiel zeigt sehr anschaulich die Komplexität, die hinter dieser Problematik steckt. Deshalb bedarf dieses Thema bei Konzipierung einer Applikation im Rahmen der Datenmodellierung großer Aufmerksamkeit!
TIP: Ein System aufeinander abgestimmter applikationsinterner Sperrmechanismen hat natürlich keine oder nicht die gewünschte Wirkung, wenn auf die Daten von außerhalb der Applikation zugegriffen wird. |
Von besonderer Bedeutung für netzwerkrelevante Porgrammteile ist die Einstellung von SET REPROCESS.
Die SET REPROCESS Einstellung legt gemeinsam mit dem aktiven Error-Handler fest, wie Visual FoxPro mit der Tatsache umgeht, daß ein Satz nicht gesperrt werden kann (siehe VFP-Hilfe zum Befehl SET REPROCESS bzw. Session D-NETZ). Zu dieser Thematik sind die folgenden Aspekte abzuwägen:
Die beiden zuletzt genannten Punkte führen zu einer programmtechnischen Umsetzung dieser Problematik, die einen hohen Allgemeinheitsgrad hat:
SET REPROCESS TO 1 llSuccess = .F. DO WHILE .T. IF RLOCK() * Sperre erfolgreich llSuccess = .T. EXIT ELSE * Sperre nicht erfolgreich IF schon diverse Wartezeit verstrichen * Nachricht anzeigen ... ENDIF IF INKEY(,"H") = -7 * Nutzer will mit der F8-Taste abbrechen EXIT ENDIF ENDIF ENDDO RETURN m.llSuccess
Voraussetzung dafür ist allerdings, daß die aufrufenden Funktionen den weiteren Programmablauf dann entsprechend des Rückgabewertes organisieren.
TIP: Programmtechnisch kann man zwar den numerischen Wert der Einstellung von SET REPROCESS abfragen, nicht aber, ob dieser Wert als "Anzahl" oder als "Sekunden" gemeint ist. In diesem Fall hilft nur eine applikationsweite Vereinbarung weiter. |
TIP: Beachten Sie im Zusammenhang mit SET REPROCESS auch die Funktion SYS(3052), die das Lockingverhalten bezüglich Index- und Memodateien regelt! |
Transaktionen spielen für die Sicherung der Konsistenz von Datenbeständen eine wichtige Rolle. Diese Thematik ist dabei nicht so sehr eine Spezifik der Netzwerkprogrammierung, sondern hat mehr mit der inhaltlichen Konsistenz zusammengehöriger Daten zu tun.
Im Rahmen der Konzipierung netzwerkfähiger Applikationen muß unbedingt beachtet werden, daß für die Schachtelung von Transaktionen lediglich 5 Ebenen zur Verfügung stehen, wovon noch eine Ebene für die Trigger-Mechanismen des Datenbankcontainers von Visual FoxPro benötigt wird. Daraus ergibt sich eine Menge von maximal 4 frei verfügbaren Schachtelungsebenen für Transaktionen.
Diese Begrenzung kann problematisch werden, wenn man ineinander geschachtelte Funktionen programmiert, die jede für sich sozusagen "der Ordnung halber" mit einer Transaktion abgesichert sind. Übersteigt diese Schachtelungstiefe die Anzahl der zur Verfügung stehenden Transaktionsebenen, wird der Fehler "BEGIN command failed. Nesting level is too deep (Error 1590)" ausgelöst!
Auch vor dieser Problematik kann man sich durch geeignete programmorganisatorische Maßnahmen schützen:
Eine dritte Transaktion neben der eben beschriebenen inhaltlichen Transaktionsebene und der Ebene für den Datenbank-Container wird für die Konsistenzsicherung des TableUpdate()-Befehls benötigt, wenn man wie empfohlen mit Table-Buffering arbeitet:
ACHTUNG! Beachten Sie bitte, das eine Transaktion nur bezogen auf ein DataSession gilt! Die weiteren allgemeinen technischen Aspekte der Arbeit mit Transaktionen in Visual FoxPro entnehmen Sie bitte der Session D-NETZ. |
Zur Programmierung multi-user- und multi-session-fähiger Applikationen gehören noch einige weitere Aspekte, die im Rahmen dieser Session nur erwähnt werden können:
Wenn mehrere Instanzen einer Maske zugelassen sind, gibt es häufig den Bedarf, diese Instanzen miteinander zu koordinieren.
Zur Koordinierung existierender Instanzen ist es notwendig, alle diese Instanzen zu ermitteln.
Dabei ist zu unterscheiden zwischen dem nachträglichen Ermitteln der existierenden Instanzen einerseits bzw. dem Mitprotokollieren des Instanzierungsprozesses andererseits.
Das nachträgliche Ermitteln existierender Instanzen einer Maske kann über mehrere Wege erfolgen:
Das Mitprotokollieren der Instanzierung erfolgt über einen sogenannten FormLoader:
Die Überwachung der existierenden Instanzen einer Maske kann aus verschiedenen Gründen wichtig sein:
Für die Ermittlung der existierenden Instanzen einer Maske gibt es verschiedene Möglichkeiten:
Beide Varianten können sowohl in die Start-Events von Masken (Init und Load) eingebaut werden als auch im Vorfeld vor dem Instanzieren zum Einsatz kommen:
Außerdem arbeitet diese Variante nur zuverlässig, wenn durch entsprechende Programmier-Disziplin sichergestellt ist, daß keine Maske ohne Benutzung des Formloaders instanziert wird.
Bei weiterführendem Interesse kann zum Autor gern Kontakt aufgenommen
werden (E-Mail-Adresse:
SFlucke@asci-consulting.com).