Aufgabenbasiertes asynchrones Programmiermodell
Sie können Leistungsengpässe vermeiden und die Reaktionsfähigkeit der Anwendung insgesamt verbessern, indem Sie asynchrone Programmierung verwenden. Allerdings können herkömmliche Verfahren zum Schreiben von asynchronen Anwendungen kompliziert sein, weshalb es schwierig ist, diese Anwendungen zu schreiben, zu debuggen und zu verwalten.
C# unterstützt mit dem asynchronen Programmieren einen vereinfachten Ansatz, der asynchrone Unterstützung in der .NET-Runtime nutzt. Der Compiler übernimmt die schwierige Arbeit, die zuvor vom Entwickler ausgeführt wurde, und die Anwendung behält eine logische Struktur bei, die synchronem Code ähnelt. So erhalten Sie alle Vorteile der asynchronen Programmierung mit einem Bruchteil des Aufwands.
Dieses Thema enthält eine Übersicht über die Verwendungsmöglichkeiten der asynchronen Programmierung sowie Links zu Supportthemen, die weitere Informationen und Beispiele enthalten.
Async verbessert die Reaktionsfähigkeit
Asynchronie ist wesentlich für potenziell blockierende Aktivitäten, z.B. Webzugriff. Der Zugriff auf eine Webressource ist manchmal langsam oder verzögert. Wenn eine solche Aktivität innerhalb eines synchronen Prozesses blockiert wird, muss die gesamte Anwendung warten. In einem asynchronen Prozess kann die Anwendung mit anderer Arbeit fortfahren, die nicht von der Webressource abhängig ist, bis die potenziell blockierende Aufgabe beendet ist.
In der folgenden Tabelle werden typische Bereichen angezeigt, in denen die asynchrone Programmierung die Reaktionsfähigkeit verbessert. Die aufgelisteten APIs von .NET und der Windows-Runtime enthalten Methoden, die die asynchrone Programmierung unterstützen.
Anwendungsbereich | .NET-Typen mit asynchronen Methoden | Windows-Runtime-Typen mit asynchronen Methoden |
---|---|---|
Webzugriff | HttpClient | Windows.Web.Http.HttpClient SyndicationClient |
Arbeiten mit Dateien | JsonSerializer StreamReader StreamWriter XmlReader XmlWriter |
StorageFile |
Arbeiten mit Bildern | MediaCapture BitmapEncoder BitmapDecoder |
|
WCF-Programmierung | Synchrone und asynchrone Vorgänge |
Asynchronität erweist sich als besonders nützlich bei Prozessen, die durch den UI-Thread ausgeführt werden, da sämtliche auf die Benutzeroberfläche bezogenen Aktivitäten sich gemeinhin diesen Thread teilen. Ist ein Prozess in einer synchronen Anwendung blockiert, werden alle blockiert. Die Anwendung reagiert nicht mehr, und Sie denken möglicherweise, es sei ein Fehler aufgetreten, wenn die Anwendung tatsächlich nur wartet.
Wenn Sie asynchrone Methoden verwenden, reagiert die Anwendung weiterhin auf die Benutzeroberfläche. Sie können beispielsweise die Fenstergröße ändern, das Fenster minimieren oder die Anwendung schließen, wenn Sie nicht warten möchten, bis sie fertig ist.
Durch den auf Asynchronie basierenden Ansatz wird eine Entsprechung für die automatische Übertragung an die Liste der Optionen hinzugefügt, die beim Entwerfen von asynchronen Vorgängen zur Auswahl stehen. Sie erhalten also alle Vorteile der herkömmlichen asynchronen Programmierung, der Aufwand des Entwicklers ist jedoch wesentlich geringer.
Das Schreiben asynchroner Methoden ist einfach
Die Schlüsselwörter async und await in C# sind das Kernstück der asynchronen Programmierung. Durch Verwendung dieser beiden Schlüsselwörter können Sie mithilfe von .NET Framework-, .NET Core- oder Windows-Runtime-Ressourcen asynchrone Methoden fast genauso einfach erstellen wie synchrone Methoden. Asynchrone Methoden, die Sie unter Verwendung des Schlüsselworts async
definieren, werden als async-Methoden bezeichnet.
Das folgende Beispiel zeigt eine Async-Methode. Fast der gesamte Code sollte Ihnen bekannt vorkommen.
Ein vollständiges WPF-Beispiel (Windows Presentation Foundation) können Sie unter Asynchrones Programmieren mit async und await in C# herunterladen.
public async Task<int> GetUrlContentLengthAsync()
{
using var client = new HttpClient();
Task<string> getStringTask =
client.GetStringAsync("https://learn.microsoft.com/dotnet");
DoIndependentWork();
string contents = await getStringTask;
return contents.Length;
}
void DoIndependentWork()
{
Console.WriteLine("Working...");
}
Sie können dem vorherigen Beispiel mehrere Methoden entnehmen. Starten Sie mit der Methodensignatur. Sie enthält den async
-Modifizierer. Der Rückgabetyp lautet Task<int>
(weitere Optionen finden Sie im Abschnitt „Rückgabetypen“). Der Methodenname endet auf Async
. Im Text der Methode gibt GetStringAsync
einen Task<string>
-Wert zurück. Das bedeutet, dass Sie string
(contents
) erhalten, wenn Sie auf die Aufgabe warten (await
). Bevor Sie auf die Aufgabe warten, können Sie Arbeiten ausführen, die nicht auf einem string
-Wert von GetStringAsync
basieren.
Achten Sie besonders auf den await
-Operator. GetUrlContentLengthAsync
wird angehalten:
GetUrlContentLengthAsync
kann erst fortgesetzt werden, wenngetStringTask
abgeschlossen ist.- In der Zwischenzeit wird die Steuerung an den Aufrufer von
GetUrlContentLengthAsync
zurückgegeben. - Die Steuerung wird hier wieder aufgenommen, sobald
getStringTask
abgeschlossen ist. - Der
await
-Operator ruft nun dasstring
-Ergebnis vongetStringTask
ab.
Die return-Anweisung gibt ein ganzzahliges Ergebnis an. Alle Methoden, die auf GetUrlContentLengthAsync
warten, rufen den Längenwert ab.
Wenn für GetUrlContentLengthAsync
zwischen dem Aufrufen von GetStringAsync
und dem Warten auf die Beendigung keine Arbeit ansteht, können Sie den Code durch Aufrufen und Warten in der folgenden Anweisung vereinfachen.
string contents = await client.GetStringAsync("https://learn.microsoft.com/dotnet");
Die folgenden Merkmale fassen zusammen, wodurch das vorherige Beispiel sich als asynchrone Methode auszeichnet:
Die Methodensignatur enthält einen
async
-Modifizierer.Der Name einer Async-Methode endet mit dem Suffix "Async".
Der Rückgabetyp ist einer der folgenden Typen:
- Task<TResult>, wenn die Methode über eine return-Anweisung verfügt, in der der Operand dem Typ
TResult
angehört. - Task, wenn die Methode keine return-Anweisung hat oder eine return-Anweisung ohne Operanden hat.
void
, wenn Sie einen asynchronen Ereignishandler schreiben.- Jeder andere Typ, der über eine
GetAwaiter
-Methode verfügt
Weitere Informationen finden Sie im Abschnitt Rückgabetypen und -parameter.
- Task<TResult>, wenn die Methode über eine return-Anweisung verfügt, in der der Operand dem Typ
Die Methode umfasst normalerweise mindestens einen
await
-Ausdruck, der einen Punkt kennzeichnet, an dem die Methode erst nach Abschluss des erwarteten asynchronen Vorgangs fortgesetzt werden kann. In der Zwischenzeit wird die Methode angehalten, und die Steuerung kehrt zum Aufrufer der Methode zurück. Im nächsten Abschnitt dieses Themas wird veranschaulicht, was am Unterbrechungspunkt geschieht.
In Asynch-Methoden verwenden Sie die bereitgestellten Schlüsselwörter und Typen, um die gewünschten Aktionen anzugeben. Der Compiler übernimmt den Rest, er verfolgt auch, was geschehen muss, wenn die Steuerung zu einem await-Punkt in einer angehaltenen Methode zurückkehrt. Einige Routinenprozesse, beispielsweise Schleifen und Ausnahmebehandlung, sind im herkömmlichen asynchronen Code möglicherweise schwierig zu handhaben. In einer Async-Methode schreiben Sie diese Elemente fast so wie in einer synchronen Lösung, und das Problem ist gelöst.
Weitere Informationen zur Asynchronität in vorherigen Versionen von .NET Framework finden Sie unter TPL und herkömmliche asynchrone .NET Framework-Programmierung.
Was geschieht in einer asynchronen Methode?
Bei der asynchronen Programmierung ist es sehr wichtig zu verstehen, wie die Ablaufsteuerung von Methode zu Methode springt. In dem folgenden Diagramm werden Sie durch den Prozess geführt:
Die Zahlen in diesem Diagramm entsprechen den folgenden Schritten, die initiiert werden, wenn eine aufrufende Methode die asynchrone Methode aufruft.
Eine aufrufende Methode ruft die asynchrone Methode
GetUrlContentLengthAsync
auf und wartet auf sie.GetUrlContentLengthAsync
erstellt eine HttpClient-Instanz und ruft die asynchrone Methode GetStringAsync auf, um den Inhalt einer Website als Zeichenfolge herunterzuladen.In
GetStringAsync
geschieht etwas, durch das die Ausführung angehalten wird. Möglicherweise muss gewartet werden, bis eine Website heruntergeladen oder eine andere blockierende Aktivität ausgeführt wurde. Um blockierende Ressourcen zu vermeiden, übergibt die MethodeGetStringAsync
die Steuerung an ihren AufruferGetUrlContentLengthAsync
.GetStringAsync
gibt Task<TResult> zurück, wobeiTResult
eine Zeichenfolge ist.GetUrlContentLengthAsync
weist dergetStringTask
-Variablen die Aufgabe zu. Die Aufgabe stellt den laufenden Prozess für den Aufruf vonGetStringAsync
dar, mit der Festlegung, dass bei Abschluss der Arbeit ein tatsächlicher Zeichenfolgenwert erzeugt wurde.Da
getStringTask
noch nicht abgewartet wurde, kannGetUrlContentLengthAsync
mit anderer Arbeit fortfahren, die nicht vom Endergebnis vonGetStringAsync
abhängt. Diese Aufgaben werden durch einen Aufruf der synchronen MethodeDoIndependentWork
dargestellt.DoIndependentWork
ist eine synchrone Methode, die ihre Arbeit ausführt und zu ihrem Aufrufer zurückkehrt.Für
GetUrlContentLengthAsync
steht keine Arbeit mehr an, die die Methode ohne ein Ergebnis vongetStringTask
ausführen kann.GetUrlContentLengthAsync
möchte als Nächstes die Länge der heruntergeladenen Zeichenfolge berechnen und zurückgeben, aber die Methode kann diesen Wert erst berechnen, wenn sie über die Zeichenfolge verfügt.Daher verwendet
GetUrlContentLengthAsync
einen await-Operator, um die Ausführung anzuhalten und die Steuerung an die Methode zu übergeben, von derGetUrlContentLengthAsync
aufgerufen wurde.GetUrlContentLengthAsync
gibt einTask<int>
-Element an den Aufrufer zurück. Die Aufgabe stellt eine Zusicherung dar, ein Ganzzahlergebnis zu erzeugen, das die Länge der heruntergeladenen Zeichenfolge ist.Hinweis
Wenn
GetStringAsync
(und dahergetStringTask
) abgeschlossen ist, bevorGetUrlContentLengthAsync
darauf wartet, verbleibt die Steuerung beiGetUrlContentLengthAsync
. Der Aufwand des Anhaltens und der Rückkehr zuGetUrlContentLengthAsync
wäre überflüssig, wenn der aufgerufene asynchrone ProzessgetStringTask
bereits abgeschlossen ist undGetUrlContentLengthAsync
nicht auf das Endergebnis warten muss.In der aufrufenden Methode wird das Verarbeitungsmuster fortgesetzt. Der Aufrufer führt möglicherweise andere Arbeit aus, die nicht von dem Ergebnis von
GetUrlContentLengthAsync
abhängt, bevor er auf dieses Ergebnis wartet, oder der Aufrufer wartet sofort auf das Ergebnis. Die aufrufende Methode wartet aufGetUrlContentLengthAsync
, undGetUrlContentLengthAsync
wartet aufGetStringAsync
.GetStringAsync
wird abgeschlossen und erstellt ein Zeichenfolgenergebnis. Das Zeichenfolgenergebnis wird von dem AufrufGetStringAsync
nicht auf die Art zurückgegeben, die Sie möglicherweise erwarten. (Beachten Sie, dass die Methode bereits in Schritt 3 eine Aufgabe zurückgegeben hat.) Stattdessen wird das Zeichenfolgenergebnis in dem Task gespeichert, der den Abschluss der Methode darstellt:getStringTask
. Der await-Operator ruft das Ergebnis vongetStringTask
ab. Die Zuweisungsanweisung weistcontents
das abgerufene Ergebnis zu.Wenn
GetUrlContentLengthAsync
über das Zeichenfolgenergebnis verfügt, kann die Methode die Länge der Zeichenfolge berechnen. Dann ist die Arbeit vonGetUrlContentLengthAsync
auch abgeschlossen, und der wartende Ereignishandler kann fortfahren. Im vollständigen Beispiel am Ende des Themas können Sie überprüfen, ob der Ereignishandler den Wert des Längenergebnisses abruft und druckt. Wenn die asynchrone Programmierung für Sie neu ist, nehmen Sie sich einen Moment Zeit, um den Unterschied zwischen synchronem und asynchronem Verhalten zu untersuchen. Eine synchrone Methode kehrt zum Aufrufer zurück, wenn ihre Arbeit abgeschlossen ist (Schritt 5). Eine asynchrone Methode hingegen gibt einen Aufgabenwert zurück, wenn die Verarbeitung angehalten wird (Schritt 3 und 6). Wenn die asynchrone Methode schließlich ihre Arbeit abgeschlossen hat, wird die Aufgabe als abgeschlossen markiert und das Ergebnis ggf. in der Aufgabe gespeichert.
API-Async-Methoden
Sie fragen sich möglicherweise, wo Methoden wie GetStringAsync
zu finden sind, die die asynchrone Programmierung unterstützen. .NET Framework 4.5 oder höher und .NET Core enthalten viele Member, die mit async
und await
funktionieren. Sie können sie am Suffix „Async“ erkennen, das an den Membernamen angefügt wird, sowie am Rückgabetyp Task oder Task<TResult>. Beispielsweise enthält die System.IO.Stream
-Klasse Methoden wie CopyToAsync, ReadAsync und WriteAsync sowie die synchronen Methoden CopyTo, Read und Write.
Die Windows-Runtime enthält außerdem viele Methoden, die Sie mit async
und await
in Windows-Apps verwenden können. Weitere Informationen finden Sie unter Threading and async programming (Threading und asynchrone Programmierung) für die UWP-Entwicklung, Asynchronous programming (Windows Store apps) (Asynchrone Programmierung für Windows Store-Apps) und Quickstart: Calling asynchronous APIs in C# or Visual Basic (Schnellstart: Aufrufen von asynchronen APIs in C# oder Visual Basic), wenn Sie frühere Versionen der Windows-Runtime verwenden.
Threads
Async-Methoden sollen nicht blockierende Vorgänge sein. Ein await
-Ausdruck in einer asynchronen Methode blockiert den aktuellen Thread nicht, während der Task, auf den gewartet wurde, ausgeführt wird. Stattdessen registriert der Ausdruck den Rest der Methode als Fortsetzung und gibt die Steuerung an den Aufrufer der Async-Methode zurück.
Durch die Schlüsselwörter async
und await
werden keine zusätzlichen Threads erstellt. Async-Methoden erfordern kein Multithreading, da eine Async-Methode nicht in einem eigenen Thread ausgeführt wird. Die Methode wird im aktuellen Synchronisierungskontext ausgeführt und verwendet Zeit im Thread nur, wenn sie aktiv ist. Sie können Task.Run zur Verschiebung CPU-gebundener Arbeit in einen Hintergrundthread verwenden, aber ein Hintergrundthread nützt nichts bei einem Prozess, der wartet, dass Ergebnisse zur Verfügung gestellt werden.
Der auf Asynchronie basierende Ansatz der asynchronen Programmierung ist vorhandenen Ansätzen in nahezu jedem Fall vorzuziehen. Insbesondere eignet sich dieser Ansatz besser für E/A-gebundene Vorgänge als die BackgroundWorker-Klasse, da der Code einfacher und kein Schutz vor Racebedingungen erforderlich ist. In Kombination mit der Methode Task.Run eignet sich die asynchrone Programmierung besser als BackgroundWorker für CPU-gebundene Vorgänge, da die asynchrone Programmierung Koordinationsdetails der Ausführung des Codes von der Arbeit trennt, die Task.Run
an den Threadpool überträgt.
Async und Await
Wenn Sie angeben, dass eine Methode eine asynchrone Methode ist, indem Sie den async-Modifizierer verwenden, aktivieren Sie die folgenden zwei Funktionen.
Die markierte async-Methode kann await verwenden, um Unterbrechungspunkte festzulegen. Der
await
-Operator informiert den Compiler, dass die Async-Methode erst über diesen Punkt hinaus fortgesetzt werden kann, wenn der abgewartete asynchrone Prozess abgeschlossen ist. In der Zwischenzeit kehrt die Steuerung zum Aufrufer der Async-Methode zurück.Die Unterbrechung einer async-Methode bei einem
await
-Ausdruck stellt keine Beendigung der Methode dar, undfinally
-Blöcke werden nicht ausgeführt.Auf die markierte Async-Methode können auch Methoden warten, die sie aufrufen.
Eine asynchrone Methode enthält in der Regel mindestens ein Vorkommen eines await
-Operators, die Abwesenheit von await
-Ausdrücken verursacht jedoch keinen Compilerfehler. Wenn eine asynchrone Methode keinen await
-Operator verwendet, um einen Unterbrechungspunkt zu markieren, wird die Methode ungeachtet des async
-Modifizierers wie eine synchrone Methode ausgeführt. Der Compiler gibt eine Warnung für solche Methoden aus.
async
und await
sind kontextbezogene Schlüsselwörter. Weitere Informationen und Beispiele finden Sie unter den folgenden Themen:
Rückgabetypen und Parameter
Eine async-Methode gibt normalerweise eine Task oder Task<TResult> zurück. Innerhalb einer async-Methode wird ein await
-Operator auf einen Task angewendet, der in einem Aufruf einer anderen async-Methode zurückgegeben wurde.
Geben Sie Task<TResult> als Rückgabetyp an, wenn die Methode eine return
-Anweisung enthält, die einen Operanden vom Typ TResult
angibt.
Sie verwenden Task als Rückgabetyp, wenn die Methode keine return-Anweisung hat oder über eine return-Anweisung verfügt, die keinen Operanden zurückgibt.
Sie können auch andere Rückgabetypen angeben, vorausgesetzt sie enthalten eine GetAwaiter
-Methode. Ein Beispiel eines solchen Typs ist ValueTask<TResult>. Er ist im NuGet-Paket System.Threading.Tasks.Extension verfügbar.
Im folgenden Beispiel wird gezeigt, wie Sie eine Methode deklarieren und aufrufen, die Task<TResult> oder Task zurückgibt:
async Task<int> GetTaskOfTResultAsync()
{
int hours = 0;
await Task.Delay(0);
return hours;
}
Task<int> returnedTaskTResult = GetTaskOfTResultAsync();
int intResult = await returnedTaskTResult;
// Single line
// int intResult = await GetTaskOfTResultAsync();
async Task GetTaskAsync()
{
await Task.Delay(0);
// No return statement needed
}
Task returnedTask = GetTaskAsync();
await returnedTask;
// Single line
await GetTaskAsync();
Jede zurückgegebene Aufgabe stellt derzeit ausgeführte Arbeit dar. Eine Aufgabe kapselt Informationen über den Zustand des asynchronen Prozesses und schließlich entweder das Endergebnis aus dem Prozess oder die Ausnahme, die durch einen Prozessfehler verursacht wird.
Eine asynchrone Methode kann auch den Rückgabetyp void
aufweisen. Dieser Rückgabetyp wird hauptsächlich zum Definieren von Ereignishandlern verwendet, wobei ein void
-Rückgabetyp erforderlich ist. Asynchrone Ereignishandler dienen häufig als Ausgangspunkt für asynchrone Programme.
Auf eine asynchrone Methode, die einen void
-Rückgabetyp aufweist, kann nicht gewartet werden, und der Aufrufer einer Methode, die „void“ zurückgibt, kann keine Ausnahmen abfangen, die von der Methode ausgelöst werden.
Eine asynchrone Methode kann keine in-, ref- oder out-Parameter deklarieren. Die Methode kann jedoch Methoden aufrufen, die solche Parameter aufweisen. Genauso kann eine async-Methode keinen Wert nach Verweis zurückgeben, obwohl sie Methoden mit ref-Rückgabewerten aufrufen kann.
Weitere Informationen und Beispiele finden Sie unter Asynchrone Rückgabetypen (C#).
Asynchrone APIs in der Windows-Runtime-Programmierung weisen einen der folgenden Rückgabetypen auf, die Tasks ähnlich sind:
- IAsyncOperation<TResult>, was Task<TResult> entspricht.
- IAsyncAction, was Task entspricht.
- IAsyncActionWithProgress<TProgress>
- IAsyncOperationWithProgress<TResult,TProgress>
Benennungskonvention
Üblicherweise sollten die Namen von Methoden, die in der Regel awaitable-Typen zurückgeben (z. B. Task
, Task<T>
, ValueTask
, ValueTask<T>
), auf „Async“ enden. Methoden, die einen asynchronen Vorgang starten, aber keinen awaitable-Typ zurückgeben, sollten nicht auf „Async“ enden, sondern mit „Begin“, „Start“ oder einem ähnlichen Verb beginnen, sodass eindeutig ist, dass diese Methode nicht das Ergebnis des Vorgangs zurückgibt bzw. auslöst.
Sie können die Konvention ignorieren, wenn ein Ereignis, eine Basisklasse oder ein Schnittstellenvertrag einen anderen Namen vorsieht. Beispielsweise sollten Sie allgemeine Ereignishandler wie OnButtonClick
nicht umbenennen.
Verwandte Artikel (Visual Studio)
Titel | Beschreibung |
---|---|
Vorgehensweise: Paralleles Erstellen mehrerer Webanforderungen mit async und await (C#) | Veranschaulicht, wie mehrere Aufgaben gleichzeitig gestartet werden. |
Asynchrone Rückgabetypen (C#) | Es werden die Typen veranschaulicht, die asynchrone Methoden zurückgeben können, und es wird erklärt, wann die einzelnen Typen geeignet sind. |
Abbrechen von Aufgaben mit einem Abbruchtoken als Signalisierungsmechanismus | Zeigt, wie die folgenden Funktionen der asynchronen Lösung hinzugefügt werden: - Abbrechen einer Aufgabenliste (C#) - Abbrechen von Aufgaben nach einem bestimmten Zeitraum (C#) - Verarbeiten asynchroner Aufgaben nach Abschluss (C#) |
Verwenden von Async für den Dateizugriff (C#) | Listet die Vorteile der Verwendung von "async" und "await" für den Zugriff auf Dateien auf und veranschaulicht sie. |
Aufgabenbasiertes asynchrones Muster | In diesem Artikel wird ein asynchrones Muster beschrieben. Dieses Muster basiert auf den Typen Task und Task<TResult>. |
Videos zur asynchronen Programmierung auf Channel 9 | Stellt Links zu einer Vielzahl von Videos über die asynchrone Programmierung bereit. |