Freigeben über


Aufgabenbasiertes asynchrones Programmiermodell

Mithilfe der asynchronen Programmierung können Sie Leistungsengpässe vermeiden und die Gesamtreaktionsfähigkeit Ihrer Anwendung verbessern. Herkömmliche Techniken zum Schreiben asynchroner Anwendungen können jedoch kompliziert sein, wodurch sie schwer zu schreiben, zu debuggen und zu verwalten sind.

C# unterstützt vereinfachte Vorgehensweise, asynchrone Programmierung, die asynchrone Unterstützung in der .NET-Laufzeit nutzt. Der Compiler erledigt die schwierige Arbeit, die der Entwickler verwendet hat, und Ihre Anwendung behält eine logische Struktur bei, die dem synchronen Code ähnelt. Daher erhalten Sie alle Vorteile der asynchronen Programmierung mit einem Bruchteil des Aufwands.

Dieses Thema enthält eine Übersicht darüber, wann und wie asynchrone Programmierung verwendet wird, und enthält Links zu Supportthemen, die Details und Beispiele enthalten.

Async verbessert die Reaktionsfähigkeit

Asynchronie ist wichtig für Aktivitäten, die potenziell blockiert werden, z. B. Webzugriff. Der Zugriff auf eine Webressource ist manchmal langsam oder verzögert. Wenn eine solche Aktivität in einem synchronen Prozess blockiert wird, muss die gesamte Anwendung warten. In einem asynchronen Prozess kann die Anwendung mit anderen Arbeiten fortfahren, die nicht von der Webressource abhängig sind, bis der potenziell blockierende Vorgang abgeschlossen ist.

Die folgende Tabelle zeigt typische Bereiche, in denen die asynchrone Programmierung die Reaktionsfähigkeit verbessert. Die aufgeführten APIs von .NET und die 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 wertvoll für Anwendungen, die auf den UI-Thread zugreifen, da in der Regel alle UI-bezogenen Aktivitäten denselben Thread gemeinsam nutzen. Wenn ein Prozess in einer synchronen Anwendung blockiert wird, werden alle blockiert. Ihre Anwendung reagiert vorübergehend nicht, und Sie könnten irrtümlicherweise schließen, dass sie fehlgeschlagen ist, obwohl sie gerade nur wartet.

Wenn Sie asynchrone Methoden verwenden, reagiert die Anwendung weiterhin auf die Benutzeroberfläche. Sie können beispielsweise die Größe eines Fensters ändern oder minimieren, oder Sie können die Anwendung schließen, wenn Sie nicht warten möchten, bis es abgeschlossen ist.

Der asynchrone Ansatz fügt das Äquivalent einer automatischen Übertragung zur Liste der Optionen hinzu, die Sie beim Entwerfen asynchroner Vorgänge auswählen können. Das heißt, Sie erhalten alle Vorteile der herkömmlichen asynchronen Programmierung, aber mit viel weniger Aufwand vom Entwickler.

Asynchrone Methoden sind einfach zu schreiben

Die async- und await-Schlüsselwörter in C# sind das Herzstück der async-Programmierung. Mithilfe dieser beiden Schlüsselwörter können Sie Ressourcen in .NET Framework, .NET Core oder der Windows-Runtime verwenden, um eine asynchrone Methode fast so einfach zu erstellen, wie Sie eine synchrone Methode erstellen. Asynchrone Methoden, die Sie mithilfe des async Schlüsselworts definieren, werden als asynchrone Methoden bezeichnet.

Das folgende Beispiel zeigt eine asynchrone Methode. Fast alles im Code sollte Ihnen vertraut sein.

Sie finden ein vollständiges Windows Presentation Foundation (WPF)-Beispiel, das aus der asynchronen Programmierung mit async und await in C# heruntergeladen werden kann.

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 verschiedene Methoden aus dem vorherigen Beispiel erlernen. Beginnen Sie mit der Methodensignatur. Es enthält den async Modifier. Der Rückgabetyp lautet Task<int> (Weitere Optionen finden Sie im Abschnitt "Rückgabetypen"). Der Methodenname endet in Async. Im Text der Methode gibt GetStringAsync einen Task<string>-Wert zurück. Das bedeutet, dass Sie await (string) erhalten, wenn Sie auf die Aufgabe warten (contents). Bevor Sie auf die Aufgabe warten, können Sie Arbeiten ausführen, die nicht von dem string von GetStringAsync abhängen.

Achten Sie besonders auf den await-Operator. GetUrlContentLengthAsync wird angehalten:

  • GetUrlContentLengthAsync kann nicht fortgesetzt werden, bis getStringTask 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 holt dann das string-Ergebnis von getStringTask.

Die return-Anweisung gibt ein ganzzahliges Ergebnis an. Alle Methoden, die auf GetUrlContentLengthAsync warten, rufen den Längenwert ab.

Wenn GetUrlContentLengthAsync keine Arbeit zwischen dem Aufruf von GetStringAsync und dem Abwarten auf dessen Abschluss durchführen kann, können Sie Ihren Code vereinfachen, indem Sie den Aufruf und das Abwarten in der folgenden einzigen Anweisung zusammenfassen.

string contents = await client.GetStringAsync("https://learn.microsoft.com/dotnet");

Die folgenden Merkmale fassen zusammen, was das vorherige Beispiel zu einer asynchronen Methode macht:

  • Die Methodensignatur enthält einen async Modifizierer.

  • Der Name einer asynchronen Methode endet nach Konvention mit einem "Async"-Suffix.

  • Der Rückgabetyp ist einer der folgenden Typen:

    • Task<TResult> wenn die Methode über eine Rückgabe-Anweisung verfügt, in der der Operand typ TResultist.
    • Task wenn die Methode keine Rückgabe-Anweisung hat oder eine Rückgabe-Anweisung ohne Operand aufweist.
    • void wenn Sie einen asynchronen Ereignishandler schreiben.
    • Jeder andere Typ mit einer GetAwaiter Methode.

    Weitere Informationen finden Sie im Abschnitt " Rückgabetypen und Parameter ".

  • Die Methode enthält in der Regel mindestens einen await Ausdruck, der einen Punkt markiert, an dem die Methode erst fortgesetzt werden kann, wenn der erwartete asynchrone Vorgang abgeschlossen ist. 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 asynchronen Methoden verwenden Sie die bereitgestellten Schlüsselwörter und Typen, um anzugeben, was Sie tun möchten, und der Compiler erledigt den Rest, einschließlich des Nachverfolgens, was passieren muss, wenn das Steuerelement zu einem Wartepunkt in einer angehaltenen Methode zurückkehrt. Einige Routineprozesse, z. B. Schleifen und Ausnahmebehandlung, können im herkömmlichen asynchronen Code schwer behandelt werden. In einer asynchronen Methode schreiben Sie diese Elemente ähnlich wie in einer synchronen Lösung, und das Problem wird gelöst.

Weitere Informationen zur Asynchronie in früheren Versionen von .NET Framework finden Sie unter TPL und die herkömmliche asynchrone .NET Framework-Programmierung.

Was geschieht in einer asynchronen Methode

Das Wichtigste bei der asynchronen Programmierung ist, wie der Steuerungsfluss von Methode zu Methode verschoben wird. Das folgende Diagramm führt Sie durch den Prozess:

Ablaufnavigation der asynchronen Ablaufsteuerung

Die Zahlen im Diagramm entsprechen den folgenden Schritten, die initiiert werden, wenn eine aufrufende Methode die asynchrone Methode aufruft.

  1. Eine aufrufende Methode ruft die GetUrlContentLengthAsync asynchrone Methode auf und wartet darauf.

  2. GetUrlContentLengthAsync erstellt eine HttpClient Instanz und ruft die GetStringAsync asynchrone Methode auf, um den Inhalt einer Website als Zeichenfolge herunterzuladen.

  3. Etwas passiert in GetStringAsync, das seinen Fortschritt aussetzt. Vielleicht muss es darauf warten, dass eine Website heruntergeladen wird oder eine andere blockierende Tätigkeit beendet ist. Um das Blockieren von Ressourcen zu vermeiden, GetStringAsync übergibt die Steuerung an seinen Aufrufer, GetUrlContentLengthAsync.

    GetStringAsync gibt eine Task<TResult>Zeichenfolge zurück, wobei TResult es sich um eine Zeichenfolge handelt, und GetUrlContentLengthAsync weist die Aufgabe der getStringTask Variablen zu. Die Aufgabe stellt den laufenden Prozess für den Aufruf von GetStringAsync dar, mit der Festlegung, dass bei Abschluss der Arbeit ein tatsächlicher Zeichenfolgenwert erzeugt wurde.

  4. Da getStringTask noch nicht abgewartet wurde, kann GetUrlContentLengthAsync mit anderen Aufgaben fortfahren, die nicht vom Endergebnis von GetStringAsync abhängig sind. Diese Arbeit wird durch einen Aufruf der synchronen Methode DoIndependentWorkdargestellt.

  5. DoIndependentWork ist eine synchrone Methode, die ihre Arbeit ausführt und zum Aufrufer zurückkehrt.

  6. GetUrlContentLengthAsync hat keine Arbeit mehr, die es ohne ein Ergebnis von getStringTask erledigen kann. GetUrlContentLengthAsync als Nächstes möchte die Methode die Länge der heruntergeladenen Zeichenfolge berechnen und zurückgeben, aber sie kann diesen Wert erst berechnen, wenn sie die Zeichenfolge erhält.

    Daher verwendet GetUrlContentLengthAsync einen Await-Operator, um seinen Fortschritt auszusetzen und die Kontrolle an die aufrufende GetUrlContentLengthAsync-Methode zu übergeben. GetUrlContentLengthAsync gibt einen Task<int> an den Aufrufer zurück. Die Aufgabe stellt eine Zusage dar, um ein ganzzahliges Ergebnis zu erzeugen, das die Länge der heruntergeladenen Zeichenfolge darstellt.

    Hinweis

    Wenn GetStringAsync (und daher getStringTask) abgeschlossen ist, bevor GetUrlContentLengthAsync darauf wartet, bleibt die Kontrolle in GetUrlContentLengthAsync. Die Kosten für das Anhalten und anschließendes GetUrlContentLengthAsync Zurückgeben würden verschwendet, wenn der aufgerufene asynchrone Prozess getStringTask bereits abgeschlossen ist und GetUrlContentLengthAsync nicht auf das Endergebnis warten muss.

    In der aufrufenden Methode wird das Verarbeitungsmuster fortgesetzt. Der Anrufer könnte andere Arbeiten ausführen, die nicht vom Ergebnis GetUrlContentLengthAsync abhängig sind, bevor er darauf wartet, oder der Anrufer könnte sofort darauf warten. Die aufrufende Methode wartet auf GetUrlContentLengthAsyncund GetUrlContentLengthAsync wartet auf GetStringAsync.

  7. GetStringAsync vervollständigt und liefert ein Ergebnis als Zeichenfolge. Das Zeichenfolgenergebnis wird vom Aufruf GetStringAsync nicht wie erwartet zurückgegeben. (Denken Sie daran, dass die Methode bereits einen Vorgang in Schritt 3 zurückgegeben hat.) Stattdessen wird das Zeichenfolgenergebnis in der Aufgabe gespeichert, die den Abschluss der Methode darstellt. getStringTask Der await-Operator ruft das Ergebnis von getStringTask ab. Die Zuweisungsanweisung weist das abgerufene Ergebnis contents zu.

  8. Wenn GetUrlContentLengthAsync das Zeichenfolgenergebnis vorliegt, kann die Methode die Länge der Zeichenfolge berechnen. Dann ist die Arbeit von GetUrlContentLengthAsync 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 Sie neu in der asynchronen Programmierung sind, nehmen Sie sich einen Moment Zeit, um den Unterschied zwischen synchronem und asynchronem Verhalten zu verstehen. Eine synchrone Methode gibt zurück, wenn ihre Arbeit abgeschlossen ist (Schritt 5), aber eine asynchrone Methode gibt einen Vorgangswert zurück, wenn die Arbeit angehalten wird (Schritte 3 und 6). Wenn die asynchrone Methode schließlich ihre Arbeit abgeschlossen hat, wird die Aufgabe als abgeschlossen markiert, und das Ergebnis (falls vorhanden) wird in der Aufgabe gespeichert.

API-asynchrone Methoden

Möglicherweise fragen Sie sich, wo Sie Methoden wie GetStringAsync finden, die die asynchrone Programmierung unterstützen. .NET Framework 4.5 oder höher und .NET Core enthalten viele Member, die mit async und await arbeiten. Sie können sie durch das Suffix "Async" erkennen, das an den Membernamen angefügt wird, und an deren Rückgabetyp Task oder Task<TResult>. Die Klasse, z. B., enthält Methoden wie System.IO.Stream, CopyToAsync und ReadAsync neben den synchronen Methoden WriteAsync, CopyTo und Read.

Die Windows-Runtime enthält auch viele Methoden, die Sie mit async und await in Windows-Apps verwenden können. Weitere Informationen finden Sie unter Threading und asynchrone Programmierung für die UWP-Entwicklung und asynchrone Programmierung (Windows Store-Apps) und Schnellstart: Aufrufen asynchroner APIs in C# oder Visual Basic , wenn Sie frühere Versionen der Windows-Runtime verwenden.

Fäden

Asynchrone Methoden sollen nicht blockierende Vorgänge sein. Ein await Ausdruck in einer asynchronen Methode blockiert den aktuellen Thread nicht, während die erwartete Aufgabe ausgeführt wird. Stattdessen meldet der Ausdruck den Rest der Methode als Fortsetzung an und gibt die Kontrolle an den Aufrufer der asynchronen Methode zurück.

Die async Schlüsselwörter await führen nicht dazu, dass zusätzliche Threads erstellt werden. Asynchrone Methoden erfordern kein Multithreading, da eine asynchrone Methode nicht im eigenen Thread ausgeführt wird. Die Methode wird im aktuellen Synchronisierungskontext ausgeführt und verwendet nur Zeit auf dem Thread, wenn die Methode aktiv ist. Sie können Task.Run verwenden, um CPU-gebundene Arbeit in einen Hintergrundthread zu verschieben, aber ein Hintergrundthread hilft nicht bei einem Prozess, der nur darauf wartet, dass die Ergebnisse verfügbar sind.

Der auf Asynchronität basierende Ansatz ist in fast jedem Fall den vorhandenen Ansätzen 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 ist die Task.Run asynchrone Programmierung besser als BackgroundWorker für CPU-gebundene Vorgänge, da die asynchrone Programmierung die Koordination beim Ausführen Ihres Codes von den Aufgaben 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 asynchronen Modifizierer verwenden, aktivieren Sie die folgenden beiden Funktionen.

  • Die markierte asynchrone Methode kann await verwenden, um Anhaltepunkte festzulegen. Der await Operator teilt dem Compiler mit, dass die asynchrone Methode nicht über diesen Punkt fortgesetzt werden kann, bis der erwartete asynchrone Prozess abgeschlossen ist. In der Zwischenzeit geht die Kontrolle an den Aufrufer der asynchronen Methode zurück.

    Das Anhalten einer asynchronen Methode bei einem await Ausdruck stellt keine Beendigung der Methode dar, und finally 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 ein oder mehrere Vorkommen eines await Operators, aber das Fehlen von await Ausdrücken verursacht keinen Compilerfehler. Wenn eine asynchrone Methode keinen Operator zum Markieren eines Anhaltepunkts verwendet await , wird die Methode trotz des async Modifizierers als 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 in den folgenden Themen:

Rückgabetypen und Parameter

Eine asynchrone Methode gibt in der Regel ein Task oder ein Task<TResult> zurück. Innerhalb einer asynchronen Methode wird ein await Operator auf eine Aufgabe angewendet, die von einem Aufruf an eine andere asynchrone Methode zurückgegeben wird.

Sie geben Task<TResult> als Rückgabetyp an, wenn die Methode eine return Anweisung enthält, die einen Operanden vom Typ TResultangibt.

Sie verwenden Task als Rückgabetyp, wenn die Methode keine Rückgabe-Anweisung hat oder eine Rückgabe-Anweisung hat, die keinen Operanden zurückgibt.

Sie können auch jeden anderen Rückgabetyp angeben, vorausgesetzt, der Typ enthält eine GetAwaiter Methode. ValueTask<TResult> ist ein Beispiel für einen solchen Typ. Sie ist im Paket "System.Threading.Tasks.Extension NuGet" verfügbar.

Das folgende Beispiel zeigt, wie Sie eine Methode deklarieren und aufrufen, die entweder 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 laufende Arbeit dar. Eine Aufgabe umfasst Informationen über den Status des asynchronen Prozesses und schließlich entweder das endgültige Ergebnis des Prozesses oder die Fehlerausnahme, die der Prozess auslöst, falls er nicht erfolgreich ist.

Eine asynchrone Methode kann auch einen void Rückgabetyp aufweisen. Dieser Rückgabetyp wird in erster Linie verwendet, um Ereignishandler zu definieren, bei denen ein void Rückgabetyp erforderlich ist. Asynchrone Ereignishandler dienen häufig als Ausgangspunkt für asynchrone Programme.

Eine asynchrone Methode mit einem void Rückgabetyp kann nicht erwartet werden, und der Aufrufer einer void-returning-Methode kann keine Ausnahmen abfangen, die von der Methode ausgelöst werden.

Eine asynchrone Methode kann keine in, ref oder out-Parameter deklarieren, aber die Methode kann Methoden aufrufen, die solche Parameter verwenden. Ebenso kann eine asynchrone Methode keinen Wert nach Verweis zurückgeben, obwohl sie Methoden mit Referenzwerten aufrufen kann.

Weitere Informationen und Beispiele finden Sie unter Async-Rückgabetypen (C#).

Asynchrone APIs in der Windows-Runtime-Programmierung weisen einen der folgenden Rückgabetypen auf, die mit Aufgaben vergleichbar sind:

Namenskonvention

Standardmäßig sollten Methoden, die häufig erwartete Typen zurückgeben (z Task. B. , Task<T>, ValueTask, ValueTask<T>), Namen aufweisen, die mit "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, in der ein Ereignis, eine Basisklasse oder ein Schnittstellenvertrag einen anderen Namen vorschlägt. Zum Beispiel sollten Sie keine allgemeinen Ereignishandler wie OnButtonClick umbenennen.

Verwandte Artikel (Visual Studio)

Titel BESCHREIBUNG
So stellen Sie mehrere Webanforderungen parallel mithilfe von asynchroner und await (C#) Veranschaulicht, wie mehrere Vorgänge gleichzeitig gestartet werden.
Asynchrone Rückgabetypen (C#) Veranschaulicht die Typen, die asynchrone Methoden zurückgeben können, und erläutert, wann jeder Typ geeignet ist.
Abbrechen von Aufgaben mit einem Abbruchtoken als Signalmechanismus. Zeigt, wie Sie Ihrer asynchronen Lösung die folgenden Funktionen hinzufügen:

- Abbrechen einer Liste von Aufgaben (C#)
- Vorgänge nach einem bestimmten Zeitraum abbrechen (C#)
- Asynchrone Aufgaben verarbeiten, sobald sie abgeschlossen sind (C#)
Verwenden von asynchronem Dateizugriff (C#) Listet und demonstriert die Vorteile der Verwendung von async und await, um auf Dateien zuzugreifen.
Aufgabenbasiertes asynchrones Muster (TAP) Beschreibt ein asynchrones Muster, das auf den Typen Task und Task<TResult> basiert.
Asynchrone Videos auf Kanal 9 Enthält Links zu einer Vielzahl von Videos zur asynchronen Programmierung.

Siehe auch