Die Task-Klasse repräsentiert eine CPU-bound oder eine IO-bound Aufgabe. Dieser Blog erklärt die wichtigsten Aspekten der Task-Klasse.
Zwei Task Typen
Zusammen mit TPL (Task Parallel Library) wurde mit .NET Framework 4.0 die Task-Klasse eingeführt. Im Kontext von TPL ist ein Task eine Abstraktion einer CPU-bound Aufgabe die durch den Thread Scheduler an einen Thread zugewiesen wird. Das kann ein Thread aus dem Threadpool sein oder ein eigener Thread (TaskCreationOptions.LongRunning).
In .NET Framework 4.5 wurde das async\await-Model für IO-bound Aufgaben eingeführt. Weil die Task-Klasse schon viele Eigenschaften hatte die es für async\await ebenfalls brauchte, hat Microsoft entschieden die Klasse zu erweitern und für async\await einzusetzen. Im Kontext von async\await wird ein Task nicht einem Thread zugewiesen. Es wird höchstens der Code nach dem await auf einem Threadpool Thread ausgeführt wenn ConfigureAwait(false) spezifiziert wurde. Doch dazu mehr in einem späteren Blog.
Es gibt also zwei Typen von Tasks die beide mit dem gleichen Konstrukt abgedeckt werden:
- Delegate Tasks (code-based tasks): für parallele CPU-bound Aufgaben. Dieser Task-Typ repräsentiert Code der parallel ausgeführt werden soll. Das passiert immer auf einem eigenen Thread.
- Promise Tasks (event-based tasks): für asynchrone IO-bound Aufgaben. Dieser Task-Typ repräsentiert ein Event oder Signal worauf gewartet werden muss. Der Code vor dem await wird auf dem aufrufenden Thread ausgeführt. Während des Wartens ist kein Thread aktiv und der Code nach dem await wird entweder auf dem aufrufenden Thread oder einen Threadpool Thread ausgeführt.
Einordnung der Lösungstechnologien
Delegate Tasks (code-based tasks): parallele Programmierung
- Data Parallelismus
-> Parallel-Klasse
-> PLINQ - Task Parallelismus
- Statischer Task Parallelismus
-> Task.Run & Task.Factory.StartNew() - Dynamischer Task Parallelismus
-> new Task()
- Statischer Task Parallelismus
Promise Tasks (event-based tasks): asynchrone Programmierung
-> EAP – Model (Event-based Asynchronous Pattern)
-> APM – Model (Asynchronous Programming Model)
-> async\await
In dieser Blog-Serie wird nur Statischer Task Parallelismus behandelt und die asynchrone Programmierung.
Diverses
Ein cold Task ist ein Task der zwar kreiert wurde (z.B. mit ‚new Task()‘) aber noch nicht durch den Task Scheduler gestartet wurde. Ein hot Task läuft bereits.
Ein Task mit Status RanToCompletion kann nicht mehr neu gestartet werden.
Die Task-Klasse
Die Klasse Task repräsentiert eine Aufgabe die keinen Rückgabewert hat, Task<T> repräsentiert eine Aufgabe mit Rückgabewert von Typ T.
Zustand eines Tasks
Sobald der Task kreiert wurde, kann man den aktuellen Status auslesen in der Eigenschaft Status.
Separat gibt es noch die Eigenschaften IsCompleted, IsCanceled und IsFaulted.
Die folgende Tabelle erklärt die Zusammenhänge:
Zustand eines delegate Tasks
Ein delegate Task kann folgende Zustände haben:
Im Normalfall wird ein Task mit Task.Run() gestartet. Rückgabe davon ist ein ‚hot Task‘, also einen Task mit Status Running. Je nach Laufzeitereignissen endet der Task mit RanToCompletion, Faulted oder Canceled. Der Task ist gekoppelt an einen Thread während den Zuständen Running und WaitingForChildrenToComplete.
Zustand eines promise Tasks
Wenn eine IO-bound Aktion am warten ist (z.B. während eines HTTP download) wird kein Code ausgeführt (es gibt kein Thread). Beim promise Task fehlen deshalb die Zustände WaitingToRun und Running. Verwirrend: Es gibt trotzdem den Zustand RanToCompletion, obwohl der Task gar nie Running war.
Ein promise Task wird immer ‚hot‘ kreiert, was heisst, dass die Operation in Bearbeitung ist. Der Zustand WaitingForActivation sollte eigentlich „InProgress“ heissen.
Result
Die Eigenschaft Result enthält den Rückgabewert von Task<T>. Der Wert steht erst zur Verfügung, wenn der Task den Status RanToCompletion hat.
Exceptions
Eventuelle Exceptions werden in der Exception Eigenschaft vom Task Objekt gespeichert. Die Information steht erst zur Verfügung, wenn der Task den Status Faulted oder Canceled hat. Weil Tasks verschachtelt sein können, wird dazu der Typ AggregateException verwendet.
Folgende Eigenschaften enthalten die Ausnahmeinformationen:
- InnerException: Typ Exception, enthält die Exception welche die Ausnahme verursacht hat.
- InnerExceptions: Typ AggregateException, hierarchisch strukturierte Sammlung von Exceptions.
Um die Hierarchie der Innerexceptions flach zu schlagen, gibt es den Aufruf ‚Flatten‘. Folgendes Beispiel erstellt eine Liste mit Ausnahmen und filtert eventuelle OperationCanceledExceptions herraus (dazu später mehr).
List<Exception> exceptions = aggregateException.Flatten().InnerExceptions.Where(e => !(e is OperationCanceledException)).ToList();
Das Exception-Handling für delegate und promise Tasks ist unterschiedlich und wird in den nachfolgenden Blogs erklärt.
Follow up
Nächstes Mal werden delegate Tasks mit TPL ausführlich behandelt. Wir schauen uns das Erstellen der Tasks, Cancellation und Exception Hanlding an.
Pingback: Noser Blog C# Concurrency Teil 6: Locking - Noser Blog
Pingback: Noser Blog C# Concurrency Teil 8: Delegate Tasks - Noser Blog
Pingback: Noser Blog C# Concurrency Teil 9: Delegate Task Cancellation - Noser Blog