Task-based Asynchronous Pattern (TAP)

Asynchrone Programmierung

Unter dem Begriff der asynchronen Programmierung versteht man das Schreiben von Code, bei welchem mehrere Teile gleichzeitig und unabhängig voneinander ausgeführt werden. Im Gegenteil spricht man von synchroner Programmierung, wenn aller Code sequenziell, also nacheinander ausgeführt wird. Man unterscheidet meist zwei typische Anwendungsfälle für asynchrone Programmierung:

I/O-bound

Input- und Output-Aktionen (I/O) erfordern meist zwar wenig Leistung, aber sind häufig mit verhältnismässig langen Wartezeiten verbunden, in denen die Applikation auf ein Ergebnis des Calls, z.B. an ein Web-API warten muss. Mittels asynchroner Programmierung können diese Aktionen in einen eigenen Task ausgelagert werden, welcher den Haupttask nicht blockiert. Dieser Task läuft nicht zwingendermassen in einem neuen Thread, denn wie gesagt wartet dieser hauptsächlich anstatt selbst Berechnungen durchzuführen.

CPU-bound

Asynchrone Programmierung kann aber auch bei sehr aufwendigen Berechnungen mit hoher CPU-Auslastung die Performance einer Applikation verbessern. Der typische Anwendungsfall ist eine Applikation mit UI, welches während aufwendiger Berechnungen kurzzeitig blockiert bzw. "laggt". Wenn man den Berechnungstask aber asynchron laufen lässt, kann sich das UI während den Berechnungen weiter aktualisieren und responsive bleiben. Anders als beim I/O-bound-Anwendungsfall wird ein solcher Task üblicherweise in einem eigenen Thread ausgeführt.

Asynchron Programmieren mit TAP

Das Prinzip der asynchronen Programmierung gibt es in vielen Programmiersprachen und Frameworks, die Implementationen unterscheiden sich jedoch teils recht stark. Alleine für C# gibt es rund drei Varianten, asynchron zu programmieren. Das "Task-based Asynchronous Pattern" (TAP) ist jedoch die neueste und auch die heute empfohlene Art.

Tasks

Wie vom Namen her wenig erstaunlich, basiert TAP auf der Klasse Task, bzw. auf deren generischen Gegenstück Task<T>. Tasks in C# sind die Implementation des "Promise Model of Concurrency", also dem Prinzip, dass asynchron laufende Tasks als "Versprechen" angesehen werden, dass das Ergebnis einer Operation jetzt noch nicht vorliegt, aber später verfügbar sein wird. Tasks dienen also dazu, zu jeder Zeit Informationen zum asynchronen Task zu erhalten, insbesondere, ob dieser noch aktiv ist, oder bereits vollständig abgearbeitet wurde.

 

Die generische Variante Task<T> ermöglicht es dass eine asynchrone Methode, zusätzlich zu den Informationen zum Task selbst auch noch einen "regulären" Wert wie man ihn von synchronen Methoden kennt zurückgeben kann. Falls das synchrone Pendant der asynchronen Methode als Rückgabewert einen string hat, hat die asynchrone Methode einen Rückgabewert von Task<string>. Synchrone Methoden, welche als Rückgabewert void haben, geben als asynchrone Methoden einfach Task zurück.

async / await

Die Schlüsselwörter async und await treten stets als Paar auf, das heisst, wenn eine Methode als async deklariert wird, wird sie auch ein await innerhalb der Methode haben. Zwar ist dies nicht zwingend, aber ohne await ist somit redundant, bzw. führt zur Durchführung von Operationen, welche ohne await nutzlos sind. Umgekehrt, wenn das await-Schlüsselwort innerhalb einer Methode verwendet wird, muss die Methode zwingendermassen als async markiert sein.

 

Das Schlüsselwort await wird verwendet, um auf die Vollendung eines zuvor gestarteten Tasks zu warten, z.B. weil die Daten, welche vom Task bereitgestellt werden, für den weiteren Programmverlauf benötigt werden. Die Ausführung des Programmes wird bei einer await-Anweisung gestoppt, bis der genannte Task abgeschlossen wurde. Zusätzlich hat await die Funktion, dass generische asynchrone Methoden, welche eigenlich Task<T> zurückgeben, direkt das entpackte Objekt T liefern, womit eine direkte Zuweisung an eine Variable dieses Typs möglich ist.

Beispiel

Im Folgenden ein einfaches Beispiel einer asynchronen I/O-bound Methode, welche Daten aus einem WebAPI bezieht. In diesem Fall ist das WebAPI das Microsoft Graph API, von welchem erweiterte Informationen mittels eines entsprechenden Services geholt werden:

Wenn während des Wartens auf die Daten keine weiteren Operationen durchgeführt werden sollen, kann dies natürlich auch kürzer geschrieben werden: