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:
- Im Logfile auf dem Webserver.
Dort wird immer Name und Oid des Workers, sowie die vollständige Exception incl. Callstack geschrieben. - Im Anwendungsprotokoll des Workers, falls verfügbar.
Dort wird Standardmäßig nur eine Fehlermeldung mit der Exception-Message ohne Callstack geschrieben. - 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 immerWaitingForStart
(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ürMaxNumberOfRestarts
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.