Asynchrone ProgrammierungAsynchronous programming

Wenn Sie E/A-gebundene Anforderungen haben (z. B. Daten aus einem Netzwerk anfordern, auf eine Datenbank zugreifen oder aus einem Dateisystem lesen oder hineinschreiben), sollten Sie asynchrone Programmierung verwenden.If you have any I/O-bound needs (such as requesting data from a network, accessing a database, or reading and writing to a file system), you'll want to utilize asynchronous programming. Sie könnten auch CPU-gebundenen Code haben, z.B. eine teure Berechnung, bei der es sich auch um ein gutes Szenario zum Schreiben von asynchronem Code handelt.You could also have CPU-bound code, such as performing an expensive calculation, which is also a good scenario for writing async code.

C# verfügt über ein asynchrones Programmiermodell auf Sprachebene, das ein problemloses Schreiben von asynchronem Code ermöglicht, ohne dass Sie Rückrufe koordinieren oder eine Bibliothek nutzen müssen, die Asynchronität unterstützt.C# has a language-level asynchronous programming model, which allows for easily writing asynchronous code without having to juggle callbacks or conform to a library that supports asynchrony. Es folgt das so genannte Aufgabenbasierte asynchrone Muster (TAP).It follows what is known as the Task-based Asynchronous Pattern (TAP).

Grundlegende Übersicht über das asynchrone ModellOverview of the asynchronous model

Der Kern der asynchronen Programmierung sind die Task- und Task<T>-Objekte, die asynchrone Vorgänge modellieren.The core of async programming is the Task and Task<T> objects, which model asynchronous operations. Sie werden von den async- und await-Schlüsselwörtern unterstützt.They are supported by the async and await keywords. Das Modell ist in den meisten Fällen recht einfach:The model is fairly simple in most cases:

  • Für E/A-gebundenen Code erwarten Sie einen Vorgang, der einen Task oder Task<T> innerhalb einer async-Methode zurückgibt.For I/O-bound code, you await an operation that returns a Task or Task<T> inside of an async method.
  • Für CPU-gebundenen Code erwarten Sie einen Vorgang, der in einem Hintergrundthread mit der Task.Run-Methode gestartet wird.For CPU-bound code, you await an operation that is started on a background thread with the Task.Run method.

Das Schlüsselwort await ist sozusagen der Zauberstab.The await keyword is where the magic happens. Es übergibt die Steuerung an den Aufrufer der Methode, die await durchgeführt hat. Somit können Benutzeroberflächen letztendlich reaktionsfähig und Dienste elastisch werden.It yields control to the caller of the method that performed await, and it ultimately allows a UI to be responsive or a service to be elastic. Obwohl es Möglichkeiten gibt, sich mit anderem asynchronem Code als async und await zu befassen, konzentriert sich dieser Artikel auf die Konstrukte auf Sprachebene.While there are ways to approach async code other than async and await, this article focuses on the language-level constructs.

E/A-gebundenes Beispiel: Herunterladen von Daten von einem WebdienstI/O-bound example: Download data from a web service

Möglicherweise müssen Sie einige Daten aus einem Webdienst herunterladen, wenn auf eine Schaltfläche geklickt wird, möchten aber den UI-Thread nicht blockieren.You may need to download some data from a web service when a button is pressed but don't want to block the UI thread. Das können Sie wie folgt erreichen:It can be accomplished like this:

private readonly HttpClient _httpClient = new HttpClient();

downloadButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI as the request
    // from the web service is happening.
    //
    // The UI thread is now free to perform other work.
    var stringData = await _httpClient.GetStringAsync(URL);
    DoSomethingWithData(stringData);
};

Der Code gibt die Absicht (Daten asynchron herunterladen) an, ohne durch Interaktion mit Task-Objekten vereitelt zu werden.The code expresses the intent (downloading data asynchronously) without getting bogged down in interacting with Task objects.

CPU-gebundenes Beispiel: Ausführen einer Berechnung für ein SpielCPU-bound example: Perform a calculation for a game

Angenommen, Sie schreiben ein mobiles Spiel, in dem ein Knopfdruck vielen Feinden auf dem Bildschirm Schaden zufügen könnte.Say you're writing a mobile game where pressing a button can inflict damage on many enemies on the screen. Das Durchführen der Schadensberechnung kann teuer sein, und sie auf dem UI-Thread durchzuführen, hält das Spiel scheinbar an, wenn die Berechnung ausgeführt wird!Performing the damage calculation can be expensive, and doing it on the UI thread would make the game appear to pause as the calculation is performed!

Die beste Möglichkeit ist, einen Hintergrundthread zu starten, der die Arbeit mit Task.Run erledigt, und das Ergebnis mit await zu erwarten.The best way to handle this is to start a background thread, which does the work using Task.Run, and await its result using await. So wird die Benutzeroberfläche nicht gestört, wenn die Berechnung durchgeführt wird.This allows the UI to feel smooth as the work is being done.

private DamageResult CalculateDamageDone()
{
    // Code omitted:
    //
    // Does an expensive calculation and returns
    // the result of that calculation.
}

calculateButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI while CalculateDamageDone()
    // performs its work. The UI thread is free to perform other work.
    var damageResult = await Task.Run(() => CalculateDamageDone());
    DisplayDamage(damageResult);
};

Dieser Code drückt die Absicht des Klickereignisses der Schaltfläche deutlich aus, und zwar in einer nicht blockierenden Weise, und erfordert kein manuelles Verwalten eines Hintergrundthreads.This code clearly expresses the intent of the button's click event, it doesn't require managing a background thread manually, and it does so in a non-blocking way.

Was im Hintergrund geschiehtWhat happens under the covers

Es gibt viele bewegliche Bestandteile bei asynchronen Vorgängen.There are many moving pieces where asynchronous operations are concerned. Wenn Sie neugierig sind, was hinter den Kulissen von Task und Task<T> geschieht, dann lesen Sie den Artikel Async im Detail.If you're curious about what's happening underneath the covers of Task and Task<T>, see the Async in-depth article for more information.

Auf der C#-Seite transformiert der Compiler Ihren Code in einen Zustandsautomaten, der z.B. die Rückgabe der Ausführung protokolliert, wenn await erreicht wird, und das Fortsetzen der Ausführung, wenn ein Hintergrundauftrag abgeschlossen ist.On the C# side of things, the compiler transforms your code into a state machine that keeps track of things like yielding execution when an await is reached and resuming execution when a background job has finished.

Für theorieinteressierte Benutzer: Dies ist eine Implementierung des Promise-Modells der Asynchronie.For the theoretically-inclined, this is an implementation of the Promise Model of asynchrony.

Wichtigste BestandteileKey pieces to understand

  • Async-Code kann für E/A-gebundenen und CPU-gebundene Code, aber für jedes Szenario anders verwendet werden.Async code can be used for both I/O-bound and CPU-bound code, but differently for each scenario.
  • Async-Code verwendet die Konstrukte Task<T> und Task, die als Modell für Arbeit im Hintergrund verwendet werden können.Async code uses Task<T> and Task, which are constructs used to model work being done in the background.
  • Das async-Schlüsselwort wandelt eine Methode in eine asynchrone Methode um, mit der Sie das await-Schlüsselwort in ihrem Nachrichtentext verwenden können.The async keyword turns a method into an async method, which allows you to use the await keyword in its body.
  • Wenn das await-Schlüsselwort angewendet wird, hält es die aufrufende Methode an, und gibt die Steuerung wieder an den Aufrufer zurück, bis die Aufgabe abgeschlossen ist.When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.
  • await kann nur innerhalb einer Async-Methode verwendet werden.await can only be used inside an async method.

Erkennen von CPU-gebundener und E/A-gebundener ArbeitRecognize CPU-bound and I/O-bound work

Die ersten beiden Beispiele in diesem Leitfaden zeigen, wie Sie async und await für E/A-gebundene und CPU-gebundene Arbeit verwenden können.The first two examples of this guide showed how you could use async and await for I/O-bound and CPU-bound work. Es ist wichtig, dass Sie erkennen können, wenn ein zu erledigender Auftrag E/A-gebunden oder CPU-gebunden ist, da dies die Leistung Ihres Codes erheblich beeinflussen und möglicherweise zum fälschlichen Gebrauch bestimmter Konstrukte führen kann.It's key that you can identify when a job you need to do is I/O-bound or CPU-bound because it can greatly affect the performance of your code and could potentially lead to misusing certain constructs.

Hier sind zwei Fragen, die Sie stellen sollten, bevor Sie Code schreiben:Here are two questions you should ask before you write any code:

  1. Wird Ihr Code auf etwas „warten“, z.B. auf Daten aus einer Datenbank?Will your code be "waiting" for something, such as data from a database?

    Wenn Ihre Antwort „Ja“ lautet, ist Ihre Arbeit E/A-gebunden.If your answer is "yes", then your work is I/O-bound.

  2. Wird Ihr Code eine umfangreiche Berechnung durchführen?Will your code be performing an expensive computation?

    Wenn Ihre Antwort „Ja“ lautet, ist Ihre Arbeit CPU-gebunden.If you answered "yes", then your work is CPU-bound.

Falls Ihre Arbeit E/A-gebunden ist, verwenden Sie async und await ohne Task.Run.If the work you have is I/O-bound, use async and await without Task.Run. Sie sollten nicht die Task Parallel Library verwenden.You should not use the Task Parallel Library. Der Grund dafür wird in Async ausführlich dargelegt.The reason for this is outlined in Async in Depth.

Falls Ihre Arbeit CPU-gebunden ist und Sie sich für Reaktionsfähigkeit interessieren, dann verwenden Sie async und await, aber übertragen Sie die Arbeit mit Task.Run auf einen anderen Thread.If the work you have is CPU-bound and you care about responsiveness, use async and await, but spawn off the work on another thread with Task.Run. Wenn die Arbeit für Parallelität und Konkurrenz geeignet ist, erwägen Sie auch die Verwendung der Task Parallel Library.If the work is appropriate for concurrency and parallelism, also consider using the Task Parallel Library.

Darüber hinaus sollten Sie immer die Ausführung Ihres Codes messen.Additionally, you should always measure the execution of your code. Sie könnten z.B. in eine Situation geraten, in der Ihre CPU-gebundene Arbeit im Vergleich zum Aufwand der Kontextwechsel beim Multithreading nicht kostspielig genug ist.For example, you may find yourself in a situation where your CPU-bound work is not costly enough compared with the overhead of context switches when multithreading. Jede Entscheidung hat Nachteile, und Sie sollten die Nachteile je nach Ihrer Situation auswählen.Every choice has its tradeoff, and you should pick the correct tradeoff for your situation.

Weitere BeispieleMore examples

Die folgenden Beispiele veranschaulichen verschiedene Möglichkeiten, wie Sie asynchronen Code in C# schreiben können.The following examples demonstrate various ways you can write async code in C#. Diese decken einige andere Szenarios ab, auf die Sie möglicherweise stoßen.They cover a few different scenarios you may come across.

Extrahieren von Daten aus einem NetzwerkExtract data from a network

Dieser Ausschnitt lädt den HTML-Code von der Homepage https://dotnetfoundation.org herunter und zählt, wie oft die Zeichenfolge „.NET“ darin vorkommt.This snippet downloads the HTML from the homepage at https://dotnetfoundation.org and counts the number of times the string ".NET" occurs in the HTML. Er verwendet ASP.NET zur Definition einer Web-API-Controllermethode, die diesen Task ausführt und die Zahl zurückgibt.It uses ASP.NET to define a Web API controller method, which performs this task and returns the number.

Hinweis

Wenn Sie eine HTML-Analyse im Produktionscode durchführen möchten, nutzen Sie dafür nicht die regulären Ausdrücke.If you plan on doing HTML parsing in production code, don't use regular expressions. Verwenden Sie stattdessen eine Analysebibliothek.Use a parsing library instead.

private readonly HttpClient _httpClient = new HttpClient();

[HttpGet, Route("DotNetCount")]
public async Task<int> GetDotNetCount()
{
    // Suspends GetDotNetCount() to allow the caller (the web server)
    // to accept another request, rather than blocking on this one.
    var html = await _httpClient.GetStringAsync("https://dotnetfoundation.org");

    return Regex.Matches(html, @"\.NET").Count;
}

Hier sehen Sie das gleiche Szenario, das für eine universelle Windows-App geschrieben wurde, die die gleiche Aufgabe ausführt, wenn auf eine Schaltfläche geklickt wird:Here's the same scenario written for a Universal Windows App, which performs the same task when a Button is pressed:

private readonly HttpClient _httpClient = new HttpClient();

private async void OnSeeTheDotNetsButtonClick(object sender, RoutedEventArgs e)
{
    // Capture the task handle here so we can await the background task later.
    var getDotNetFoundationHtmlTask = _httpClient.GetStringAsync("https://dotnetfoundation.org");

    // Any other work on the UI thread can be done here, such as enabling a Progress Bar.
    // This is important to do here, before the "await" call, so that the user
    // sees the progress bar before execution of this method is yielded.
    NetworkProgressBar.IsEnabled = true;
    NetworkProgressBar.Visibility = Visibility.Visible;

    // The await operator suspends OnSeeTheDotNetsButtonClick(), returning control to its caller.
    // This is what allows the app to be responsive and not block the UI thread.
    var html = await getDotNetFoundationHtmlTask;
    int count = Regex.Matches(html, @"\.NET").Count;

    DotNetCountLabel.Text = $"Number of .NETs on dotnetfoundation.org: {count}";

    NetworkProgressBar.IsEnabled = false;
    NetworkProgressBar.Visibility = Visibility.Collapsed;
}

Warten auf das Abschließen mehrerer TasksWait for multiple tasks to complete

Sie könnten sich in einer Situation befinden, in der Sie mehrere Daten gleichzeitig abrufen müssen.You may find yourself in a situation where you need to retrieve multiple pieces of data concurrently. Die Task-API enthält die Methoden Task.WhenAll und Task.WhenAny, mit denen Sie asynchronen Code schreiben können, der einen nicht blockierenden Wartevorgang für mehrere Hintergrundaufträge durchführt.The Task API contains two methods, Task.WhenAll and Task.WhenAny, that allow you to write asynchronous code that performs a non-blocking wait on multiple background jobs.

Dieses Beispiel zeigt, wie Sie einen User-Datensatz für einen Satz von userId nehmen könnten.This example shows how you might grab User data for a set of userIds.

public async Task<User> GetUserAsync(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)
{
    var getUserTasks = new List<Task<User>>();
    foreach (int userId in userIds)
    {
        getUserTasks.Add(GetUserAsync(userId));
    }

    return await Task.WhenAll(getUserTasks);
}

Hier sehen Sie eine weitere Möglichkeit, dies mithilfe von LINQ präziser zu schreiben:Here's another way to write this more succinctly, using LINQ:

public async Task<User> GetUserAsync(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static async Task<User[]> GetUsersAsync(IEnumerable<int> userIds)
{
    var getUserTasks = userIds.Select(id => GetUserAsync(id));
    return await Task.WhenAll(getUserTasks);
}

Auch wenn es weniger Code ist, sollten Sie trotzdem vorsichtig sein, wenn Sie LINQ mit asynchronem Code mischen.Although it's less code, use caution when mixing LINQ with asynchronous code. Da LINQ verzögerte (lazy) Ausführung verwendet, werden asynchrone Aufrufe nicht sofort ausgeführt, so wie in einer foreach-Schleife, es sei denn, Sie erzwingen, dass die generierte Sequenz einen Aufruf von .ToList() oder .ToArray() durchläuft.Because LINQ uses deferred (lazy) execution, async calls won't happen immediately as they do in a foreach loop unless you force the generated sequence to iterate with a call to .ToList() or .ToArray().

Wichtige Informationen und HinweiseImportant info and advice

Bei asynchroner Programmierung sind einige Details zu berücksichtigen, durch die ein unerwartetes Verhalten verhindert werden kann.With async programming, there are some details to keep in mind which can prevent unexpected behavior.

  • async -Methoden benötigen ein Schlüsselwort await in Ihrem Textkörper, andernfalls erfolgt niemals eine Rückgabe!async methods need to have an await keyword in their body or they will never yield!

    Berücksichtigen Sie dies.This is important to keep in mind. Wenn await nicht im Textkörper einer async-Methode verwendet wird, generiert der C#-Compiler eine Warnung, aber der Code wird kompiliert und ausgeführt, als ob es sich um eine normale Methode handeln würde.If await is not used in the body of an async method, the C# compiler generates a warning, but the code compiles and runs as if it were a normal method. Dies ist unglaublich ineffizient, da der Zustandsautomat, der vom C#-Compiler für die asynchrone Methode generiert wird, nichts erreicht.This is incredibly inefficient, as the state machine generated by the C# compiler for the async method is not accomplishing anything.

  • Sie sollten „Async“ als Suffix für die Namen aller async-Methoden hinzufügen, die Sie schreiben.You should add "Async" as the suffix of every async method name you write.

Mit dieser in .NET verwendeten Konvention kann leichter zwischen synchronen und asynchronen Methoden unterschieden werden.This is the convention used in .NET to more easily differentiate synchronous and asynchronous methods. Bestimmte, von Ihrem Code nicht explizit aufgerufene Methoden (z. B. Ereignishandler oder Webcontrollermethoden) werden nicht unbedingt angewendet.Certain methods that aren't explicitly called by your code (such as event handlers or web controller methods) don't necessarily apply. Da sie von Ihrem Code nicht explizit aufgerufen werden, ist es nicht wichtig, ihre Namen explizit anzugeben.Because they are not explicitly called by your code, being explicit about their naming isn't as important.

  • async void sollte nur für Ereignishandler verwendet werden.async void should only be used for event handlers.

async void ist die einzige Möglichkeit, mit der asynchrone Ereignishandler ausgeführt werden können, da Ereignisse keine Rückgabetypen haben (und somit Task und Task<T> nicht verwenden können).async void is the only way to allow asynchronous event handlers to work because events do not have return types (thus cannot make use of Task and Task<T>). Jede andere Verwendung der async void folgt nicht dem TAP-Modell und kann schwierig zu verwenden sein, wie beispielsweise:Any other use of async void does not follow the TAP model and can be challenging to use, such as:

  • Ausnahmen in einer async void-Methode können nicht außerhalb der Methode abgefangen werden.Exceptions thrown in an async void method can't be caught outside of that method.

  • async void-Methoden sind schwierig zu testen.async void methods are difficult to test.

  • async void-Methoden können schlechte Nebeneffekte verursachen, wenn der Aufrufende nicht erwartet, dass sie asynchron sind.async void methods can cause bad side effects if the caller isn't expecting them to be async.

  • Gehen Sie bei der Verwendung von asynchronen Lambdaausdrücken in LINQ-Ausdrücken sorgfältig vorTread carefully when using async lambdas in LINQ expressions

Lambdaausdrücke in LINQ verwenden verzögerte Ausführung. Das bedeutet, dass Code zu einem Zeitpunkt ausgeführt werden kann, zu dem Sie es nicht erwarten.Lambda expressions in LINQ use deferred execution, meaning code could end up executing at a time when you're not expecting it to. Die Einführung von blockierenden Aufgaben kann schnell zu einem Deadlock führen, wenn diese nicht ordnungsgemäß geschrieben werden.The introduction of blocking tasks into this can easily result in a deadlock if not written correctly. Darüber hinaus kann die Schachtelung von asynchronem Code die Ausführung des Codes erschweren.Additionally, the nesting of asynchronous code like this can also make it more difficult to reason about the execution of the code. Async und LINQ sind leistungsstark, sollten zusammen aber so sorgfältig und deutlich wie möglich verwendet werden.Async and LINQ are powerful but should be used together as carefully and clearly as possible.

  • Schreiben Sie Code, der Aufgaben in einer nicht blockierenden Art und Weise erwartetWrite code that awaits Tasks in a non-blocking manner

Wenn Sie den aktuellen Thread blockieren, um auf den Abschluss eines Task zu warten, kann dies zu Deadlocks und blockierten Kontextthreads führen, und eine komplexere Fehlerbehandlung kann erforderlich sein.Blocking the current thread as a means to wait for a Task to complete can result in deadlocks and blocked context threads and can require more complex error-handling. Die folgende Tabelle enthält Anleitungen zum nicht-blockierenden Warten auf Tasks:The following table provides guidance on how to deal with waiting for tasks in a non-blocking way:

Verwenden Sie...Use this... Anstatt...Instead of this... Wenn Sie dies tun möchten...When wishing to do this...
await Task.Wait oder Task.ResultTask.Wait or Task.Result Abrufen des Ergebnisses einer HintergrundaufgabeRetrieving the result of a background task
await Task.WhenAny Task.WaitAny Warten auf das Abschließen einer AufgabeWaiting for any task to complete
await Task.WhenAll Task.WaitAll Warten auf das Abschließen aller AufgabenWaiting for all tasks to complete
await Task.Delay Thread.Sleep Warten auf einen ZeitraumWaiting for a period of time
  • Verwenden Sie ggf. ValueTask .Consider using ValueTask where possible

Das Zurückgeben eines Task-Objekts von asynchronen Methoden kann Leistungsengpässe in bestimmten Pfaden verursachen.Returning a Task object from async methods can introduce performance bottlenecks in certain paths. Task ist ein Verweistyp, seine Verwendung bedeutet also das Zuordnen eines Objekts.Task is a reference type, so using it means allocating an object. In Fällen, in denen eine mit dem async-Modifizierer deklarierte Methode ein zwischengespeichertes Ergebnis zurückgibt oder synchron abschließt, können die zusätzlichen Zuordnungen viel Zeit bei der Ausführung kritischer Codeabschnitte kosten.In cases where a method declared with the async modifier returns a cached result or completes synchronously, the extra allocations can become a significant time cost in performance critical sections of code. Es kann kostspielig werden, wenn diese Zuordnungen in engen Schleifen auftreten.It can become costly if those allocations occur in tight loops. Weitere Informationen finden Sie unter Generalisierte asynchrone Rückgabetypen.For more information, see generalized async return types.

  • Verwenden Sie ggf. ConfigureAwait(false).Consider using ConfigureAwait(false)

Eine häufige Frage ist: „Wann sollte ich die Task.ConfigureAwait(Boolean)-Methode verwenden?“.A common question is, "when should I use the Task.ConfigureAwait(Boolean) method?". Die Methode ermöglicht einer Task-Instanz, ihren Awaiter zu konfigurieren.The method allows for a Task instance to configure its awaiter. Dies ist ein wichtiger Aspekt, und die falsche Festlegung kann möglicherweise Auswirkungen auf die Leistung und sogar Deadlocks zur Folge haben.This is an important consideration and setting it incorrectly could potentially have performance implications and even deadlocks. Weitere Informationen zu ConfigureAwait finden Sie im ConfigureAwait-FAQ.For more information on ConfigureAwait, see the ConfigureAwait FAQ.

  • Schreiben eines weniger statusbehafteten CodesWrite less stateful code

Machen Sie sich nicht abhängig vom Zustand globaler Objekte oder der Ausführung bestimmter Methoden.Don't depend on the state of global objects or the execution of certain methods. Seien Sie stattdessen nur abhängig von Rückgabewerten der Methoden.Instead, depend only on the return values of methods. Warum?Why?

  • Code wird leichter verständlich sein.Code will be easier to reason about.
  • Code wird leichter zu testen sein.Code will be easier to test.
  • Das Kombinieren von asynchronem und synchronem Code ist wesentlich einfacher.Mixing async and synchronous code is far simpler.
  • Racebedingungen können in der Regel ganz vermieden werden.Race conditions can typically be avoided altogether.
  • Je nach Rückgabewerten ist das Koordinieren von asynchronem Code einfach.Depending on return values makes coordinating async code simple.
  • (Bonus) funktioniert hervorragend mit Abhängigkeitsinjektion.(Bonus) it works really well with dependency injection.

Ein empfohlenes Ziel ist das vollständige oder nahezu vollständige Erreichen referenzieller Transparenz in Ihrem Code.A recommended goal is to achieve complete or near-complete Referential Transparency in your code. Dies führt zu einer sehr vorhersagbaren, getesteten und verwaltbaren Codebasis.Doing so will result in an extremely predictable, testable, and maintainable codebase.

Weitere RessourcenOther resources