Implementierung

Das System läuft auf zwei verschiedenen Geräten. Bei einem der beiden Geräte handelt es sich um einen Pocket PC. Aus Gründen der Darstellung, aber auch weil bestimmte Funktionalität des .NET Frameworks auf diesem System gar nicht zur Verfügung steht, muss für beide eine eigene Lösung realisiert werden. Nachdem die Funktionalität jedoch weitgehend identisch ist wird die Software in einen systemunabhängigen Teil, und zwei systemabhängige Teile zerlegt. Abbildung 1 illustriert welche Funktionen die einzelnen Teile erfüllen und wie sie aufgeteilt sind.

Abbildung 1: Aufteilung der Systemfunktionen

 

Basispaket

Das Basispaket umfasst alle systemunabhängigen Teile. Allgemein ist zu sagen, dass es die Systemfunktionalität modellhaft abbildet, aber keinerlei darstellungsspezifische Implementierung enthält. Es wird als Unterprojekt entwickelt und zu den beiden Endsystemen als eigene .DLL- Assembly gelinkt. In den folgenden Absätzen werden die Bestandteile des Basispakets kurz beschrieben. Abbildung 2 zeigt eine Klassenübersicht des Basispakets mit deren öffentlichen Schnittstellen.

Abbildung 2: Klassenübersicht des Basispakets

 

Task Management

Dieser Teil umfasst im Wesentlichen zwei Teile. Zum einen die Implementierung eines Tasks selbst und zum anderen die des Managers, der die Hierarchisierung der Tasks durch speichern in einer Baumförmigen Struktur erreicht.

Ein Task wird im System über eine Struktur mit eben diesen Namen dargestellt. Die Struktur beinhaltet zusätzlich zu spezifischen Daten wie Name oder geplanter Startzeitpunkt auch eine dynamische Liste, welche für die Speicherung von Zeitpunkten genutzt werden kann. Die Struktur implementiert darüber hinaus eine Vergleichbarkeit von Objekten dieses Typs. Dazu wird ein Feld namens „taskID“ verwendet, das in Verbindung mit dem optionalen Feld „instanceID“ eine eindeutige Identifizierung des Objekts möglich macht. Weiters implementiert die Struktur Task eine manuelle Serialisierung. Wie im betreffenden Kapitel noch einmal näher beschrieben hat das mit der fehlenden Unterstützung automatischer Serialisierung im .NET Compact Framework zu tun. Das Speichern und Laden eines Tasks ist unter Zuhilfenahme der Klassen BinaryReader beziehungsweise BinaryWriter möglich.

Der TaskManager ist die Klasse welche die bereits erwähnte Management Funktionalität implementiert. Um die hierarchische Struktur herzustellen wird die in der Klassenbibliothek vorhandene (und unter anderem beim TreeView benützte) Klasse TreeNodeCollection verwendet. Um die Struktur Task mit der Klasse TreeNode kompatibel zu machen wird eine Wrapperklasse mit dem Namen „Task2TreeNode“ verwendet. Das Konzept ist von einer effektiven Darstellung noch strikt getrennt, denn alle verwendeten Klassen sind auf Modellebene implementiert. Nichts desto trotz lässt sich durch Verwendung dieser Klassen eine sehr schnelle und einfache Darstellung in einem TreeView erzielen. Der TaskManager wird im Großen und Ganzen nur dazu benötigt um Tasks komfortabel in die TreeNodeCollection einbringen zu können. Darüber hinaus implementiert aber natürlich auch diese Klasse eine manuelle Serialisierung über entsprechende öffentliche Funktionen.

 

Planung

Die Planungsaufgaben werden von der Klasse Scheduler wahrgenommen. Bei der Planung geht es darum konkrete Tasks in verschiedenen Zuständen zu halten. So wir ein neuer Task standardmäßig entweder als „scheduled“, also irgendwann auszuführen, oder als „planned“, also zu einem bestimmten Zeitpunkt auszuführen, in den Scheduler aufgenommen. Von diesen Zuständen aus ist es möglich den Task aktiv zu schalten oder wieder zu löschen. Aktivierte Tasks können wieder pausiert, gelöscht oder beendet werden. Wenn ein Task den Zustand „finished“ erreicht, wird er aus dem Scheduler entfernt und an den Log- Teil übergeben. Abbildung 3 zeigt welche Zustände ein Task innerhalb von der Klasse Scheduler einnehmen kann. Es ist aus jedem, mit Ausnahme des „finished“ Zustandes möglich einen Task aus diesem Subsystem zu löschen.

Alle Tasks in den Zuständen „planned“, „paused“ und „scheduled“ werden innerhalb der Klasse in Listen verwaltet, während der aktive Task als Einzelelement über ein Property erreichbar ist. Tasks im Zustand „finished“ hingegen werden ohnehin bereits an die nächste Verarbeitungsstufe weitergereicht.

Der Scheduler ist wie später beschrieben in der Lage seinen Zustand über einen BinaryWriter zu speichern und wiederherzustellen.

Abbildung 3: Zustände eines Tasks im Scheduler

 

Log und Analysen

Dieser Teil wird von einer Klasse namens HistoryLog realisiert. Sie erfüllt einerseits die Funktionalität, einen beendeten Task in ein Log- File zu schreiben. Andererseits realisiert sie die Methoden die nötig sind um die Daten wieder aus jener Log- Datei zu extrahieren.

Ein neuer Task wird immer, ohne Veränderung an das Ende der Datei angehängt. Dadurch kommt es natürlich zu einer chronologischen Reihung der abgearbeiteten Tasks.

Beim Extrahieren der Daten vom File werden immer alle Tasks nacheinander geladen, überprüft ob sie gewissen Kriterien entsprechen, und so dies der Fall ist, werden sie in das Array der gelieferten Tasks eingehängt. Über entsprechende Methoden ist es möglich Kriterien zu definieren. Diese Kriterien beschränken sich in einem ersten Schritt auf den Zeitraum für den ein Tasks geliefert werden sollen. Diese werden als Array zurückgegeben und können über eine zweite Phase durch Angabe eines Array mit den gewünschten Task - Schablonen noch einmal gefiltert werden. Diese zweite Filterung ist aus Gründen der Ordnung über Methoden der utils Bibliothek durchzuführen.

 

Synchronisation

Diese Funktionalität wird von der Klasse ActivitySynchronizer implementiert. Diese liefert sowohl die Möglichkeit die ausgeführten Aktivitäten in einer Log- Datei abzulegen, sowie die Aktivitäten aus der Log- Datei des anderen Gerätes nachzuführen. Grundsätzlich wird ein System verwendet, das auf atomare Einzelereignisse aufbaut. Diese Ereignisse werden auf jedem Gerät einzeln geloggt, wobei sich die Log- Dateien im Austauschverzeichnis einer Synchronisationssoftware befinden. Dadurch erhält die Anwendung am jeweils anderen Gerät nach einer Dateiübertragung den Aktivitätsbestand der nachgeführt werden muss. Das Nachführen ist so implementiert, dass die Geräte am Ende einen identischen Datenbestand aufweisen.

Aufgrund der verwendeten Synchronisationstechnik ist das System auf maximal zwei Geräte beschränkt. Genaueres zur verwendeten Technik kann in einem späteren Kapitel zu diesem Thema nachgelesen werden.

 

Speicherung von Aufgaben

Um die Anwendung möglichst Speicher- und Hauptspeicher- sparend zu gestalten, wurde bereits in einer der ersten Designentscheidungen beschlossen auf die Verwendung von Datenbanken zu verzichten. Dies gilt sowohl für den PC als auch für den Pocket PC Teil weil möglichst auf die Einheitlichkeit der, in beiden Varianten verwendeten, Techniken geachtet wurde.

Im Grunde ist die Serialisierung von Klassen im .NET Framework sehr einfach und komfortabel gelöst. So reicht es im Allgemeinen das Attribut [Serializable] auf eben jene Klasse anzuwenden, oder das Interface ISerializable zu implementieren. Anschließend kann sie über einen BinaryFormatter einfach in eine Datei geschrieben werden. Da jedoch keine der drei vorher genannten Komponenten in Compact Framework implementiert ist, ist eine manuelle Speicherung (über BinaryWriter/BinaryReader) unumgänglich. Dabei wird durchgängig eine relativ einfache Strategie verfolgt. Jeder komplexe Datentyp muss in seine primitiven Einzelteile zerlegt werden welche dann über den Writer auf den Stream geschrieben werden können.

Name

Format

Beschreibung

Name

String

Name; abgelegt durch BinaryWriter mit führender Längenangabe (Int32)

Description

String

Beschreibung; abgelegt wie Name

Location

String

Ort; abgelegt wie Name

taskID

Int64

Ticks bis zur Erstellungszeit des Tasks

instanceID

Int64

ID der laufenden Instanz (Zeit der Instanzierung)

PreNotificationMinutes

Int32

Minuten der vorzeitigen Erinnerung

PlannedMinutes

Int32

Geplante Dauer in Minuten

PlannedStart

Int64

Ticks bis zum geplanten Start des Tasks

TimeList.Count

Int32

Anzahl der folgenden Zeitangaben (wiederum in Ticks; Anfangs, Pause und Endzeiten)

TimeList[x]

Int64

Ticks bis zur Zeitangabe

Tabelle 1: Serialisierungsformat und Reihenfolge der Struktur Task

Tabelle 1 beschreibt das Serialisierungsformat der Struktur Task. Die einzelnen Felder werden in dieser Reihenfolge, als binärer Record abgelegt. Die folgenden Unterkapitel werden sich mit dem Aufbau der einzelnen, im System vorhanden, Dateien beschäftigen.

 

Speicherung der Aufgabenhierarchie

Die Hierarchie wird im System von der Klasse TaskManager verwaltet. Diese hält die einzelnen Aufgaben in einer baumartigen Struktur. Um diese Struktur auf eine Datei abzubilden wird eine Möglichkeit verwendet die auf den Tiefenwert des einzelnen Elements sowie auf der Reihenfolge beruht. Die einzelnen Elemente werden dabei entsprechend einer Tiefensuche traversiert und in dieser Reihenfolge zusammen mit ihrer Tiefe im Baum abgespeichert. Durch diese Konvention ist es möglich, den Baum auch eindeutig wiederherzustellen.

Die Daten werden im binären Format in der Datei abgelegt. Vor jeder Aufgabe wird der Tiefenwert als 32Bit Integer abgelegt. Anschließend folgt der Task, welcher der oben bereits angeführten Konvention folgt.

Es ist jedoch darauf zu achten, dass dieser Ansatz nicht auf fragmentiertes Lesen eines Baumes ausgelegt ist. Es wird also davon ausgegangen, dass sich immer der gesamte Knotenbestand zur Laufzeit im Baum befindet.

Beispiel 1 soll die Vorgangsweise noch einmal exemplarisch skizzieren. Wobei die in Klammer geschriebenen Ausdrücke jeweils für einen Task stehen. Natürlich ist zu beachten, dass bei der effektiven Speicherung das Binärformat verwendet wird.

a.
Ebene 1_1    
Ebene 1_2 Ebene 2_1  
  Ebene 2_2 Ebene 3_1
    Ebene 3_2
  Ebene 2_3  
Ebene 1_3    

b.

0(Ebene1_1)0(Ebene1_2)1(Ebene2_1)1(Ebene2_2)2(Ebene3_1)2(Ebene3_2)1(Ebene2_3)0(Ebene1_3)

 

 

 

 

 

 

 

Beispiel 1: a. Baumstruktur b. resultierende Speicherungsreihenfolge

 

Speichern geplanter Aufgaben

Die Verwaltung geplanter Aufgaben wird im System über die Klasse Scheduler gehandhabt. Dieser unterscheidet zischen folgenden Arten von geplanten Aufgaben: Es gibt erstens den zurzeit laufenden Task (laut Definition kann immer nur ein Task laufen). Außerdem gibt es die Listen der geplanten Tasks, welche an keine fixen Zeitangaben gebunden sind, sowie die vorausgeplanten Tasks, welche einen fixen Startzeitpunkt haben. Zu guter letzt gibt es noch die pausierten Tasks, welche bereits vorher schon einmal ausgeführt worden sind. Diese vier Arten von geplanten Tasks werden gemeinsam in eine binäre Recorddatei abgelegt. Tabelle X illustriert den Aufbau dieser Datei.

Name

Format

Beschreibung

isTaskRunning

bool

Gibt an ob derzeit ein Task läuft, demnach also ob currentTask einen vernünftigen Wert enthält.

currentTask

Task

Laufender Task falls ein solcher existiert (isTaskRunning), oder vorher gelaufener Task.

scheduledTasks.Count

Int32

Anzahl der folgenden geplanten Tasks.

scheduledTasks

{Task}

Vorher angegebene Menge an Tasks. Geplante Tasks ohne Zeitangabe.

plannedTasks.Count

Int32

Anzahl der folgenden vorausgeplanten Tasks.

plannedTasks

{Task}

Vorher angegeben Menge an Tasks. Geplante Tasks mit Zeitangabe.

pausedTasks.Count

Int32

Anzahl der folgenden pausierten Tasks.

pausedTasks

{Task}

Vorher angegeben Menge an Tasks. Pausierte Tasks.

Tabelle 2: Daten des Schedulers- Dateiaufbau

 

Speicherformat der Log-Dateien/Sync-Dateien

Das System führt sowohl auf Seite des mobilen Gerätes, wie auch auf Seite des PCs eine Log- Datei. In dieser Datei werden alle ausgeführten Einzelaktivitäten mitgeführt damit diese später vom jeweils anderen Gerät nachgezogen werden können. Die lokale Log-Datei wird in Folge als Log-Datei tituliert während die des anderen Gerätes als Sync-Datei bezeichnet wird. Für eine Liste der ausführbaren Einzelaktivitäten siehe das Kapitel über Synchronisation. Hier sei jedoch gesagt, dass es zwei Arten von Aktivitäten gibt, und zwar solche die nur einen und solche die zwei Tasks als Argument benötigen.

Name

Format

Beschreibung

timeStamp

Int64

Zeitstempel bis zu dem die Sync- Datei (Log des anderen Gerätes) bereits abgearbeitet wurde.

Type

Int16

Typ der Aktivität. Dieser Wert gibt an welche Aktion ausgeführt wurde. Außerdem bestimmt er wie viele Tasks als Argument benötigt werden. Siehe Kapitel über Synchronisation.

Task 1

Task

Erstes Argument einer Aktivität.

Task 2

[Task]

Optional. Zweites Argument einer Aktivität.

Tabelle 3: Serialisierungsformat einer Aktivität

Jede geloggte Aktivität wird mit einem Zeitstempel versehen. Dieser Zeitstempel wird jeweils vor der Aktivität in der Datei abgelegt. Um sicherzugehen, dass eine Synchronisation nicht doppelt durchgeführt wird, wird am Beginn der eigenen Log-Datei mitgeschrieben, bis zu welchem solchen Stempel bereits gelesen wurde. Für genauere Einzelheiten zur Synchronisation möchte ich an dieser Stelle wiederum auf das eigene Kapitel zu diesem Thema verweisen . Tabelle X illustriert wie eine Aktivität in einer Datei abgelegt wird. Tabelle X zeigt darauf aufbauend die Struktur einer Log- bzw. Sync- Datei. Je nachdem ob eine Aktivität zwei oder einen Task als Argument benötigt wird eben diese Anzahl abgelegt. Die Anzahl wird also über den Typ der Aktivität festgelegt und ist im Programm kodiert. Eine Datei kann beliebig viele Synchronisationseinträge enthalten.

Name

Format

Beschreibung

synchronizedTo

Int64

Zeitstempel bis zu dem die Sync- Datei (Log des anderen Gerätes) bereits abgearbeitet wurde.

Aktivitäten

{Aktivität}

Beliebige Anzahl von Aktivitäten

Tabelle 4: Struktur einer Log- bzw. Sync- Datei

 

Synchronisation

Das System besteht aus zwei Teilen. Einer wird auf einem PC ausgeführt und ist vorwiegend für Planung und Auswertung zuständig. Der Andere läuft auf einem mobilen Gerät. Trotzdem haben beide Teile beinahe denselben Funktionsumfang und benötigen daher auch dieselben Daten. Um dies zu gewährleisten wird eine Synchronisation auf Basis von abgearbeiteten Einzelaktivitäten implementiert. Die Komponente des Basispakets die diese Aufgabe übernimmt trägt den Namen ActivitySynchronizer und ist sowohl für das Loggen wie auch für die Synchronisation selbst zuständig. Sowohl der PC, als auch das mobile Gerät führen hierzu eine Liste der ausgeführten Aktivitäten mit. Diese Listen werden in Log- Dateien gespeichert. Diese Dateien werden wiederum in den Austauschordner der verwendeten Standard Synchronisationssoftware (z.B. ActiveSync) gespeichert. Dadurch ist der Log des jeweils anderen Geräts, nach einer Synchronisation der Dateien, zugreifbar. Die Log- Datei des anderen Gerätes wird in Folge als Sync- Datei bezeichnet, denn sie enthält die Einzelaktivitäten, die nachgeführt werden müssen. In Tabelle X sind die einzelnen Aktivitäten aufgeschlüsselt. Jede Aktivität ist als atomares Ereignis zu verstehen, das es nachzuziehen gilt. Dabei ist manchmal zu beachten, welche Auswirkungen die am Gerät bisher ausgeführten Aktivitäten auf die neu Hinzukommende haben. Grundsätzlich kann man zwischen zwei Arten von Aktivitäten unterscheiden. Solche, die zwei Tasks als Argument benötigen, und solche mit nur einem Task. Beispielsweise wird beim Einfügen eines neuen Tasks in eine Hierarchie sowohl der Vaterknoten, wie auch der Knoten selbst benötigt. Daher benötigt diese Aktivität auch zwei Tasks als Argument. Die Aktivität des Löschens eines Tasks aus der Hierarchie benötigt hingegen nur einen Task als Argument. Jede Aktivität bezieht sich explizit auf eine Komponente des Basispakets. Sie wurde von dieser Komponente ausgelöst und ist im Normalfall auch am anderen Gerät wieder an diese Komponente zu übergeben.

Name

Tasks

Komponente

Beschreibung

ADD2ROOT

1

TaskManager

Hinzufügen eines Tasks zur Hierarchie, wobei in die unterste Ebene (Root) eingefügt wird.

ADD

2

TaskManager

Hinzufügen eines Tasks zur Hierarchie, wobei diesmal an beliebiger Stelle (außer Root) eingefügt wird. Daher muss der Vaterknoten mitgeschrieben werden

REMOVE

1

TaskManager

Löschen eines Tasks aus der Hierarchie.

UPDATE

2

TaskManager

Aktualisieren der Daten eines Tasks in der Hierarchie. Alter Stand wird mitgegeben.

ADD2SCHED

1

Scheduler

Hinzufügen eines Tasks zur Planung.

REMOVESCHED

1

Scheduler

Entfernen eines Tasks aus der Planung.

ENDCURRTASK

1

HistoryLog

Beenden des zurzeit ausgeführten Tasks.

PAUSETASK

1

Scheduler

Pausieren des derzeit ausgeführten Tasks.

START

1

Scheduler

Aktivsetzen eines Tasks.

Tabelle 5: Typen von Aktivitäten

Wie bereits im Kapitel zum Log- Datei Format beschrieben wird jede Aktivität aus einer Kombination eines Zeitstempels, einer Kennzeichnung des Typs und der jeweiligen Anzahl benötigter Tasks als Argumente abgespeichert. Der Zeitstempel wird dabei dazu benötigt um angeben zu können wie weit die Sync- Datei bereits bearbeitet wurde. Diese Information, also der letzte gelesene Zeitstempel aus der Synchronisations- Datei, wird am Anfang der eigenen Log- Datei abgelegt. So ist es möglich die eigene Log- Datei zu kürzen, sobald die Datei des anderen Gerätes bearbeitet wird. Der Ablauf einer Synchronisation ist also wie folgt:

 

Rahmenbedingungen bei der Synchronisation

Aufgrund der Art und Weise wie das Kürzen der Log- Dateien gehandhabt wird ist die Synchronisierung auf zwei Endgeräte beschränkt. Andere Synchronisierungsverfahren wären sicherlich denkbar, aber mit wesentlich mehr Ressource- Aufwand verbunden. Der ActivitySynchronizer als ausführendes Organ ist im Basispaket implementiert und arbeitet gleichwertig für alle Komponenten als Sammelstelle für ausgeführte Aktivitäten.

Am TaskManager ist die Synchronisierung komplett implementiert. Daher können alle Aktivitäten des Managers am anderen Gerät nachgeführt werden. Zu einem Konkurrenzfall kommt es hierbei nur, wenn von einem Gerät ein Task gelöscht wird an dem, oder dessen Untertasks, das andere Gerät etwas ändern wollte. Entsprechend anderen ähnlichen Systemen wird hier eine höhere Priorität der Löschfunktion angenommen. Das Gesamtsystem landet also in einem Zustand, in dem auf beiden Geräten der entsprechende Task gelöscht wurde.

Auch für den Scheduler ist die Synchronisierung komplett implementiert. Aufgrund der internen Struktur des Schedulers können jedoch Probleme auftreten, wenn auf beiden Geräten gleichzeitig Aktivitäten am selben Task durchgeführt werden. Das kommt daher, dass bei Aktionen im Scheduler meistens Zeiten in der Liste der Tasks mitgeschrieben werden. Zur Lösung des Problems bieten sich zwei Ansätze an. Zum einen könnte man gewisse Aktionen nur auf einem Gerät zulassen und zum anderen könnte man einem Endgerät eine höhere Priorität zugestehen, wodurch sich dessen Änderungen durchsetzen. Das Nachfragen beim Benutzer ist deshalb nicht möglich weil es sich um eine asynchrone Synchronisierung handelt und der Datenbestand auf jeden Fall zu jedem Zeitpunkt konsistent sein muss. Die derzeitige Implementierung realisiert erstere Möglichkeit. So ist es auf einem Gerät nur möglich Tasks zu planen, während das andere (mobile) Gerät über alle Funktionen des Schedulers verfügen kann. Die Aktivität des Beendens des eben laufenden Tasks ist eigentlich auch eine Aktion des Schedulers, wird jedoch dem HistoryLog zugeschrieben, weil die Aktion einen Task vom Scheduler zum HistoryLog verschiebt. Damit muss der Logger schlussendlich mit der Aktion umgehen können. Trotzdem gelten auch für diese Aktivität die vorher beschriebenen Eigenschaften des Schedulers und sie kann nur vom mobilen Gerät aus angestoßen werden.

Der letzte hier beschriebene Eckpfeiler bei der implementierten Synchronisation ist, dass während die Synchronisationsroutine läuft, das Loggen von Aktivitäten deaktiviert ist. Dadurch kommt es nicht zu fehlerhaften, sich aufschaukelnden loggen ein und derselben Aktivität. Andererseits darf aber auch kein Event dazu führen, dass während dieser Zeit Aktionen ausgeführt werden, denn diese würden dem Log entgehen.

 

Letzte Änderung: 14.07.2006, 13:00