Asynchrone ProgrammierungAsynchronous programming

Wenn Sie E/A-gebundene Anforderungen (z.B. Daten aus einem Netzwerk anfordern oder auf eine Datenbank zugreifen) haben, sollten Sie asynchrone Programmierung verwenden.If you have any I/O-bound needs (such as requesting data from a network or accessing a database), 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# bietet ein auf Sprachebene asynchrones Programmiermodell, das das Schreiben von asynchronem Code problemlos ermöglicht, ohne Rückrufe jonglieren oder eine Bibliothek bestätigen zu müssen, die Asynchronie 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 which 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 ModellBasic Overview 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 are 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 wenden Sie await auf einen Vorgang an, der Task oder Task<T> innerhalb einer async-Methode zurückgibt.For I/O-bound code, you await an operation which returns a Task or Task<T> inside of an async method.

Für CPU-gebundenen Code wenden Sie await auf einen Vorgang an, der in einem Hintergrundthread mit der Task.Run-Methode gestartet wird.For CPU-bound code, you await an operation which 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.

Es gibt weitere Methoden als Ansatz für asynchronen Code als async und await, die im oben verknüpften TAP-Artikel beschriebenen sind, aber dieses Dokument konzentriert sich ab nun auf die Konstrukte auf Sprachebene.There are other ways to approach async code than async and await outlined in the TAP article linked above, but this document will focus on the language-level constructs from this point forward.

E/A-Beispiel: Herunterladen von Daten von einem WebdienstI/O-Bound Example: Downloading data from a web service

Sie müssen möglicherweise einige Daten von einem Webdienst herunterladen, wenn auf eine Schaltfläche geklickt wird, möchten aber nicht den UI-Thread 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 kann einfach wie folgt erreicht werden:It can be accomplished simply 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);
};

Und das ist schon alles!And that’s it! Der Code gibt die Absicht (einige Daten asynchron herunterladen) an, ohne durch Interaktion mit Taskobjekten vereitelt zu werden.The code expresses the intent (downloading some data asynchronously) without getting bogged down in interacting with Task objects.

CPU-gebundenes Beispiel: Ausführen einer Berechnung eines SpielsCPU-bound Example: Performing 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, dies zu verarbeiten, ist die Arbeit mit einen Hintergrundthread, der Task.Run verwendet, und await als Ergebnis hat.The best way to handle this is to start a background thread which does the work using Task.Run, and await its result. Dadurch wird die Benutzeroberfläche nicht gestört, wenn die Berechnung durchgeführt wird.This will allow 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);
};

Und das ist schon alles!And that's it! Dieser Code drückt die Absicht des click-Ereignisses der Schaltfläche sauber aus und benötigt kein manuelles Verwalten eines Hintergrundthreads. Dies geschieht in einer nicht blockierenden Art und Weise.This code cleanly 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's a lot of 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 für weitere Informationen.If you're curious about what's happening underneath the covers of Task and Task<T>, checkout 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 which 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 dieser Anleitung 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 can use async and await for I/O-bound and CPU-bound work. Der Schlüssel, den Sie erkennen können, wenn ein zu erledigender Auftrag E/A-gebunden oder CPU-gebunden ist, kann die Leistung Ihres Codes erheblich beeinträchtigen und möglicherweise zum Missbrauch bestimmter Konstrukte führen.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 a very 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 ist im Artikel Async ausführlich dargestellt.The reason for this is outlined in the Async in Depth article.

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 auf einen anderen Thread mit Task.Run.If the work you have is CPU-bound and you care about responsiveness, use async and await but spawn the work off on another thread with Task.Run. Wenn die Arbeit für Parallelität und Konkurrenz geeignet ist, sollten Sie auch über die Verwendung der Task Parallel Library nachdenken.If the work is appropriate for concurrency and parallelism, you should 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 NetzwerkExtracting Data from a Network

Dieser Ausschnitt lädt den HTML-Code von www.dotnetfoundation.org herunter und zählt die Häufigkeit, mit der die Zeichenfolge „.NET“ im HTML-Code auftritt.This snippet downloads the HTML from www.dotnetfoundation.org and counts the number of times the string ".NET" occurs in the HTML. Er verwendet ASP.NET MVC zur Definition einer Webcontrollermethode, die diese Aufgabe ausführt, indem sie die Zahl zurückgibt.It uses ASP.NET MVC to define a web controller method which performs this task, returning 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> GetDotNetCountAsync()
{
    // Suspends GetDotNetCountAsync() to allow the caller (the web server)
    // to accept another request, rather than blocking on this one.
    var html = await _httpClient.DownloadStringAsync("http://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 SeeTheDotNets_Click(object sender, RoutedEventArgs e)
{
    // Capture the task handle here so we can await the background task later.
    var getDotNetFoundationHtmlTask = _httpClient.GetStringAsync("http://www.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 SeeTheDotNets_Click, returning control to its caller.
    // This is what allows the app to be responsive and not hang on 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 aller AufgabenWaiting 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 which allow you to write asynchronous code which 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 etwas präziser zu schreiben:Here's another way to write this a bit 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);
}

Obwohl es weniger Code ist, sollten Sie trotzdem vorsichtig sein, wenn Sie LINQ mit asynchronem Code mischen.Although it's less code, take care 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

Asynchrone Programmierung ist relativ einfach, es sind jedoch einige Details zu berücksichtigen, die unerwartetes Verhalten verhindern können.Although async programming is relatively straightforward, there are some details to keep in mind which can prevent unexpected behavior.

  • async -Methoden benötigen ein await -Schlüsselwort in Ihrem Textkörper, oder sie werden nie zurückgeben!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 im Textkörper einer async-Methode nicht 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 will generate a warning, but the code will compile and run as if it were a normal method. Beachten Sie, dass dies auch sehr ineffizient wäre, da der Zustandsautomat, der vom C#-Compiler für die asynchrone Methode generiert wurde, nichts erreichen würde.Note that this would also be incredibly inefficient, as the state machine generated by the C# compiler for the async method would not be 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.

Dies ist die in .NET verwendete Konvention, mit der leichter zwischen synchronen und asynchronen Methoden unterschieden werden kann.This is the convention used in .NET to more-easily differentiate synchronous and asynchronous methods. Beachten Sie, dass bestimmte Methoden, die von Ihrem Code (z.B. Ereignishandler oder Webcontrollermethoden) nicht explizit aufgerufen werden, nicht unbedingt angewendet werden.Note that certain methods which aren’t explicitly called by your code (such as event handlers or web controller methods) don’t necessarily apply. Da diese von Ihrem Code nicht explizit aufgerufen werden, ist es nicht wichtig, ihre Namen explizit anzugeben.Because these 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 sehr schwierig zu testen.async void methods are very difficult to test.
  • async void-Methoden können große 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 aber so sorgfältig und genau wie möglich zusammen 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 einer Aufgabe zu warten, kann es zu Deadlocks und blockierten Kontextthreads kommen und wesentlich komplexere Fehlerbehandlung erfordern.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 significantly more complex error-handling. Die folgende Tabelle enthält Anleitungen zum nicht-blockierenden Warten auf Aufgaben: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öchtenWhen 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
  • 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