void WorkItemCreating()

Bevor ein (noch nicht laufendes) Work-Item vom Work-Manager eingeplant oder aktualisiert wird, wird die Methode aufgerufen. Darin kann das Work-Item eigene Eigenschaften berechnen, die mehr Informationen brauchen als dem Konstruktor zur Verfügung stehen.

Die Standardimplementierung ist leer.

void Run()

Hier muss die eingentliche Arbeit implementiert werden.

void WorkItemShouldFinish()

Diese Methode wird aufgerufen, wenn ein laufendes Work-Item abgebrochen werden soll. Das gibt dem Work-Item die Möglichkeit, asynchron auf ein solches Event zu reagieren, und beispielsweise ein ManualResetEvent zu setzen. Alternativ kann aber auch innerhalb der Implementierung von Run regelmäßig die Bedingung !IsRunning geprüft und ggf. die Bearbeitung abgebrochen werden.

Der Aufruf erfolgt mit einem der Status ShutdownRequest, CancelledBySystem oder CancelledByUser. Falls mehrere Abbruchgründe eintreten, kann die Methode sogar mehrfach aufgerufen werden. Allerdings kann ein Grund nur durch einen wichtigeren ersetzt werden. Der wichtigste ist CancelledByUser.

Die Standardimplementierung schreibt eine Warnung über dieses Ereignis ins Applikationsprotokoll, falls dieses aktiv ist.

void WorkItemFinished()

Diese Methode wird für jedes Work-Item genau einmal ausgeführt, das der Work-Manager einmal zu Gesicht bekommen hat. Was dann tatsächlich passiert, ist der Eigenschaft “State” zu entnehmen:

Status Beschreibung Standardimplementierung
Finished Die Methode Run ist ordnungsgemäß zurückgekehrt oder das Work-Item wurde zwar abgebrochen, aber es war sowieso gerade fertig (CurrentProgress == MaxProgress) keine Aktion
Removed Das Work-Item war schon einmal eingeplant und wurde abgebrochen, bevor es starten konnte. keine Aktion
ShutdownConfirmed Die Anwendung wird gerade heruntergefahren und das Work-Item hat sich auf Aufforderung beendet. Das Work-Item wird neu eingeplant (für nach dem nächsten Neustart).
Timeout Das Work-Item hat die maximale Verarbeitungsdauer überschritten und sich daraufhin beendet. Solange MaxNumberOfRestarts noch nicht erreicht ist, wird das WorkItem neu eingeplant.
Die Aktion wird mit LogCriticalError protokolliert.
Killed Das Work-Item hat die maximale Verarbeitungsdauer überschritten und sich nicht binnen der gesetzten Nachfrist beendet. Der Vorfall wird mit LogCriticalError protokolliert.
Cancelled Das Work-Item hat auf eine Abbruchanforderung durch den Benutzer reagiert und sich selbst beendet. keine Aktion
Aborted Die Ausführung wurde durch einen Anwendungsneustart unerwartet unterbrochen.
Dieser Aufruf erfolgt natürlich erst nach dem Neustart der Anwendung.
Das Work-Item wird neu eingeplant.
Error Die Run-Methode ist mit einer unbehandelten Ausnahmen abgebrochen. Diese steht in ExceptionDuringRun. Solange MaxNumberOfRestarts noch nicht erreicht ist wird das WorkItem neu eingeplant.
Der Fehler wird mit LogCriticalError protokolliert.

Wenn sich das Work-Item nach der Ausführung der Methode neu eingeplant hat, wechselt der Status automatisch von Finished auf Reschedule, von ShutdownConfirmed auf ShutdownRestart, von Aborted auf AbortedRestart, von Timeout auf TimeoutRetry bzw. von Error auf ErrorRetry.

Überschriebene Implementierungen sollten im allgemeinen base.WorkItemFinished() in den Fällen aufrufen, die sie nicht selbst explizit behandelt haben. Nur dadurch ist sichergestellt, dass Worker z.B. nach einem Anwendungsneustart auch wirklich wieder starten.

Ferner darf die Methode nicht selbst Save aufrufen. Das würde die transaktionale Integrität brechen. Das Work-Item wird danach immer automatisch gespeichert.

Beispiel, um nach einem (bestätigten) Timeout beliebig oft und ohne Karenzzeit dazwischen neu zu starten – das entspricht logisch Thread.Yield():

protected override void WorkItemFinished()
{
    if (State == EnumWorkItemState.Timeout)
        AddSuccessor(TimeSpan.Zero);
    else
        base.WorkItemFinished();
}

void InitLogger()

Diese Methode wird vor der eigentlichen Ausführung eines Work-Items aufgerufen, wenn IsLoggingEnabled aktiviert ist. Alternativ kann die Methode auch selbst aufgerufen werden, wenn eine verzögerte bzw. bedingte Anlage des Protokolls gewünscht ist. Dann muss LoggerGuid leer sein.

Die Standardimplementierung legt ein neues Applikationsprotokoll an, wenn LoggerGuid leer ist oder öffnet ein bestehendes Protokoll wenn einer LoggerGuid angegeben wurde. Nachher ist LoggerGuid immer gefüllt. Dadurch ist sichergestellt, dass nach Neustarts im selben Protokoll fortgefahren wird.

Die Methode kann überschrieben werden, um abweichende Logging-Verfahren zu implementieren. beispielsweise täglich rollierende Logs.

void LogCriticalError(Exception ex, string message = null)

Damit wird eine kritische, also nicht sinnvoll behandelbare Ausnahme protokolliert. Die Protokollierung erfolgt standardmäßig an 3 Stellen:

  1. Im Logfile auf dem Webserver.
    Dort wird immer Name und Oid des Workers, sowie die vollständige Exception incl. Callstack geschrieben.
  2. Im Anwendungsprotokoll des Workers, falls verfügbar.
    Dort wird Standardmäßig nur eine Fehlermeldung mit der Exception-Message ohne Callstack geschrieben.
  3. An die UI. Wenn es sich um ein Work-Item eines Benutzers handelt, wird dieser per Server-Toast auf den Fehler hingewiesen. Bei System-Work-Items erfolgt die Signalisierung an alle User. Auch hier ist die Fehlermeldung ohne Callstack.

Optional kann ein abweichender Meldungstext angegeben werden. Dieser kann per Platzhalter auf Name, Oid, und Exception-Message zugreifen, Siehe XmlDoc.

WorkItemBase CreateSuccsessor()

Das ist faktisch eine Klon-Methode. Sie erstellt eine Kopie des Work-Items. Dabei werden alle persistierten Eigenschaften des Work-Items übernommen, mit folgenden Ausnahmen:

  • State ist immer WaitingForStart (Standard für neue Items)
  • ScheduledStartTime wird auf die aktuelle Uhrzeit gesetzt, also sofort einplanen.
  • NumberOfStarts wird nicht übernommen. Das muss explizit selbst gemacht werden, wenn erwünscht ist, dass bei dieser Neueinplanung der Countdown für MaxNumberOfRestarts läuft.

Eigene Worker können diese Methode überschreiben, um ein abweichendes Verhalten zu implementieren. Das gilt dann für alle per AddSuccessor erzeugten Nachfolger einschließlich automatischer Neustarts nach Fehlern.

Beim Überschreiben sollte immer zuerst die Standardimplementierung base.CreateSuccessor() aufgerufen werden.