Suche…


Einführung

In C # blockiert eine als async deklarierte Methode nicht innerhalb eines synchronen Prozesses, wenn Sie E / A-basierte Vorgänge verwenden (z. B. Webzugriff, Arbeiten mit Dateien, ...). Das Ergebnis solcher async-markierter Methoden kann über die Verwendung des await Schlüsselworts await .

Bemerkungen

Eine async Methode kann void Task oder Task<T> .

Der Rückgabetyp Task wartet, bis die Methode abgeschlossen ist, und das Ergebnis wird void . Task<T> gibt einen Wert vom Typ T nachdem die Methode abgeschlossen ist.

async Methoden sollten Task oder Task<T> im Gegensatz zu void in fast allen Fällen zurückgeben. async void methoden können nicht await , was zu einer vielzahl von problemen führt. Das einzige Szenario, in dem ein async void sollte void ist im Fall eines Event-Handlers.

async / await funktioniert, indem Sie Ihre async Methode in eine Zustandsmaschine umwandeln. Dazu erstellt es hinter den Kulissen eine Struktur, in der der aktuelle Status und beliebige Kontexte (wie lokale Variablen) MoveNext() , und eine MoveNext() Methode MoveNext() wird, um den MoveNext() (und den zugehörigen Code auszuführen), wenn ein erwartetes Erwarten beendet wird.

Einfache aufeinanderfolgende Anrufe

public async Task<JobResult> GetDataFromWebAsync()
{
  var nextJob = await _database.GetNextJobAsync();
  var response = await _httpClient.GetAsync(nextJob.Uri);
  var pageContents = await response.Content.ReadAsStringAsync();
  return await _database.SaveJobResultAsync(pageContents);
}

Hierbei ist zu beachten, dass jede await Methode asynchron aufgerufen wird und die Steuerung für den Zeitpunkt des Aufrufs an das System zurückgegeben wird. Der Fluss in der Methode ist jedoch linear und erfordert keine besondere Behandlung Asynchronität. Wenn eine der aufgerufenen Methoden fehlschlägt, wird die Ausnahme wie erwartet verarbeitet. Dies bedeutet, dass die Ausführung der Methode abgebrochen wird und die Ausnahme in den Stack aufgenommen wird.

Versuchen Sie / Catch / Schließlich

6,0

Ab C # 6.0 kann das await Schlüsselwort jetzt innerhalb eines catch und finally Blocks verwendet werden.

try {
   var client = new AsyncClient();
   await client.DoSomething();
} catch (MyException ex) {
   await client.LogExceptionAsync();
   throw;
} finally {
   await client.CloseAsync();
}
5,0 6,0

Vor C # 6.0 müssten Sie etwas wie folgt tun. Beachten Sie, dass 6.0 die Nullprüfungen auch mit dem Operator für die Nullpropagierung bereinigt hat.

AsynClient client;
MyException caughtException;
try {
     client = new AsyncClient();
     await client.DoSomething();
} catch (MyException ex) {
     caughtException = ex;
}

if (client != null) {
    if (caughtException != null) {
       await client.LogExceptionAsync();
    }
    await client.CloseAsync();
    if (caughtException != null) throw caughtException;
}

Wenn Sie auf eine Aufgabe warten, die nicht von async erstellt wurde (z. B. eine von Task.Run erstellte Task.Run ), können einige Debugger an Ausnahmen brechen, die von der Task ausgelöst werden, selbst wenn sie scheinbar vom umgebenden try / catch behandelt werden. Dies geschieht, weil der Debugger es in Bezug auf Benutzercode als nicht behandelt betrachtet. In Visual Studio gibt es eine Option namens "Just My Code" , die deaktiviert werden kann, damit der Debugger in solchen Situationen nicht beschädigt wird.

Web.config-Setup für das korrekte async-Verhalten auf 4,5 setzen.

Die web.config system.web.httpRuntime muss das Ziel 4.5 sein, um sicherzustellen, dass der Thread den Anforderungskontext durchläuft, bevor die async-Methode fortgesetzt wird.

<httpRuntime targetFramework="4.5" />

Async und wait haben in ASP.NET vor 4.5 ein undefiniertes Verhalten. Async / await wird in einem beliebigen Thread fortgesetzt, der möglicherweise keinen Anforderungskontext hat. Anwendungen unter Last schlagen nach dem Zufallsprinzip fehl, mit Ausnahme von NULL-Referenzzugriffen, die nach dem Erwarten auf HttpContext zugreifen. Die Verwendung von HttpContext.Current in WebApi ist aufgrund von Async gefährlich

Gleichzeitige Anrufe

Es ist möglich, auf mehrere Anrufe gleichzeitig zu warten, indem zuerst die wartbaren Aufgaben aufgerufen und dann auf sie gewartet werden.

public async Task RunConcurrentTasks()
{
    var firstTask = DoSomethingAsync();
    var secondTask = DoSomethingElseAsync();

    await firstTask;
    await secondTask;
}

Alternativ kann Task.WhenAll verwendet werden, um mehrere Tasks in einem einzigen Task zu gruppieren, der abgeschlossen ist, wenn alle übergebenen Tasks abgeschlossen sind.

public async Task RunConcurrentTasks()
{
    var firstTask = DoSomethingAsync();
    var secondTask = DoSomethingElseAsync();

    await Task.WhenAll(firstTask, secondTask);
}

Sie können dies auch innerhalb einer Schleife tun, zum Beispiel:

List<Task> tasks = new List<Task>();
while (something) {
    // do stuff
    Task someAsyncTask = someAsyncMethod();
    tasks.Add(someAsyncTask);
}

await Task.WhenAll(tasks);

Um Ergebnisse von einer Aufgabe zu erhalten, nachdem mehrere Aufgaben mit Task.WhenAll abgewartet wurden, warten Sie einfach erneut auf die Aufgabe. Da die Aufgabe bereits abgeschlossen ist, wird das Ergebnis einfach zurückgegeben

var task1 = SomeOpAsync();
var task2 = SomeOtherOpAsync();

await Task.WhenAll(task1, task2);

var result = await task2;

Mit Task.WhenAny können auch mehrere Tasks parallel ausgeführt werden, z. B. Task.WhenAll , mit dem Unterschied, dass diese Methode abgeschlossen wird, wenn eine der angegebenen Tasks ausgeführt wird.

public async Task RunConcurrentTasksWhenAny()
{
    var firstTask = TaskOperation("#firstTask executed");
    var secondTask = TaskOperation("#secondTask executed");
    var thirdTask = TaskOperation("#thirdTask executed");
    await Task.WhenAny(firstTask, secondTask, thirdTask);
}

Die Task zurückgegeben durch RunConcurrentTasksWhenAny wird abgeschlossen , wenn ein firstTask , secondTask oder thirdTask abgeschlossen ist .

Warten Sie auf den Operator und das async-Schlüsselwort

await Operator und das async Schlüsselwort zusammenkommen:

Die asynchrone Methode, in der await verwendet wird, muss durch das Schlüsselwort async geändert werden.

Das Gegenteil trifft nicht immer zu: Sie können eine Methode als async kennzeichnen, ohne in ihrem Körper zu await .

Was await wird, ist die Ausführung des Codes bis zum Abschluss der erwarteten Task auszusetzen. auf jede aufgabe kann gewartet werden.

Hinweis: Sie können nicht auf eine asynchrone Methode warten, die nichts zurückgibt (nichtig).

Tatsächlich ist das Wort 'suspends' etwas irreführend, da nicht nur die Ausführung angehalten wird, sondern der Thread möglicherweise für die Ausführung anderer Operationen frei wird. Das await wird unter der Haube durch ein wenig Compiler-Zauber umgesetzt: Es teilt eine Methode in zwei Teile - vor und nach dem await . Der letzte Teil wird ausgeführt, wenn die erwartete Aufgabe abgeschlossen ist.

Wenn wir einige wichtige Details ignorieren, erledigt der Compiler dies grob für Sie:

public async Task<TResult> DoIt()
{
    // do something and acquire someTask of type Task<TSomeResult>  
    var awaitedResult = await someTask;
    // ... do something more and produce result of type TResult
    return result;
}

wird:

public Task<TResult> DoIt()
{
    // ...
    return someTask.ContinueWith(task => {
        var result = ((Task<TSomeResult>)task).Result;
        return DoIt_Continuation(result);
    });
}

private TResult DoIt_Continuation(TSomeResult awaitedResult)
{
    // ...
}

Jede übliche Methode kann auf folgende Weise in asynchron umgewandelt werden:

await Task.Run(() => YourSyncMethod());

Dies kann vorteilhaft sein, wenn Sie eine lange ausgeführte Methode im UI-Thread ausführen müssen, ohne die UI einzufrieren.

Es gibt jedoch eine sehr wichtige Bemerkung: Asynchron bedeutet nicht immer gleichzeitig (parallel oder sogar multithreadig). async - await erlaubt selbst in einem einzigen Thread immer noch asynchronen Code. Sehen Sie sich beispielsweise diesen benutzerdefinierten Taskplaner an . Ein solcher "verrückter" Task-Scheduler kann einfach Aufgaben in Funktionen umwandeln, die innerhalb der Message-Loop-Verarbeitung aufgerufen werden.

Wir müssen uns fragen: Welcher Thread führt die Fortsetzung unserer Methode DoIt_Continuation ?

Der await Operator plant standardmäßig die Ausführung der Fortsetzung mit dem aktuellen Synchronisierungskontext . Dies bedeutet, dass standardmäßig für WinForms und WPF die Fortsetzung im UI-Thread ausgeführt wird. Wenn Sie dieses Verhalten aus irgendeinem Grund ändern müssen, verwenden Sie die Methode Task.ConfigureAwait() :

await Task.Run(() => YourSyncMethod()).ConfigureAwait(continueOnCapturedContext: false);

Eine Aufgabe zurückgeben, ohne abzuwarten

Methoden, die asynchrone Operationen ausführen, brauchen nicht zu await wenn:

  • Es gibt nur einen asynchronen Aufruf innerhalb der Methode
  • Der asynchrone Aufruf ist am Ende der Methode
  • Es ist nicht notwendig, Ausnahmen zu erfassen / zu behandeln, die innerhalb der Task auftreten können

Betrachten Sie diese Methode, die eine Task zurückgibt:

public async Task<User> GetUserAsync(int id)
{
    var lookupKey = "Users" + id;

    return await dataStore.GetByKeyAsync(lookupKey);
}

Wenn GetByKeyAsync dieselbe Signatur wie GetUserAsync (Rückgabe einer Task<User> ), kann die Methode vereinfacht werden:

public Task<User> GetUserAsync(int id)
{
    var lookupKey = "Users" + id;

    return dataStore.GetByKeyAsync(lookupKey);
}

In diesem Fall ist das Verfahren nicht markiert werden müssen , async , obwohl es einen asynchronen Vorgang ist Vorformen. Die Aufgabe von zurück GetByKeyAsync wird direkt an die rufenden Methode übergeben, wo es wird await hrsg.

Wichtig : Wenn Sie die Task anstatt auf sie zu warten, wird das Ausnahmeverhalten der Methode geändert, da die Ausnahme nicht innerhalb der Methode ausgelöst wird, die die Task startet, sondern in der Methode, die sie erwartet.

public Task SaveAsync()
{
    try {
        return dataStore.SaveChangesAsync();
    }
    catch(Exception ex)
    {
        // this will never be called
        logger.LogException(ex);
    }
}

// Some other code calling SaveAsync()

// If exception happens, it will be thrown here, not inside SaveAsync()
await SaveAsync();

Dies verbessert die Leistung, da der Compiler die Erzeugung einer zusätzlichen asynchronen Zustandsmaschine erspart .

Das Blockieren von asynchronem Code kann zu Deadlocks führen

Es ist eine schlechte Praxis, asynchrone Aufrufe zu blockieren, da dies in Umgebungen mit Synchronisationskontext Deadlocks verursachen kann. Die beste Vorgehensweise ist, async / await "ganz nach unten" zu verwenden. Beispielsweise verursacht der folgende Windows Forms-Code einen Deadlock:

private async Task<bool> TryThis()
{
    Trace.TraceInformation("Starting TryThis");
    await Task.Run(() =>
    {
        Trace.TraceInformation("In TryThis task");
        for (int i = 0; i < 100; i++)
        {
            // This runs successfully - the loop runs to completion
            Trace.TraceInformation("For loop " + i);
            System.Threading.Thread.Sleep(10);
        }
    });

    // This never happens due to the deadlock
    Trace.TraceInformation("About to return");
    return true;
}

// Button click event handler
private void button1_Click(object sender, EventArgs e)
{
    // .Result causes this to block on the asynchronous call
    bool result = TryThis().Result;
    // Never actually gets here
    Trace.TraceInformation("Done with result");
}

Nach Abschluss des asynchronen Aufrufs wartet er im Wesentlichen darauf, dass der Synchronisationskontext verfügbar wird. Der Event-Handler "behält" jedoch den Synchronisationskontext bei, während er auf den TryThis() der TryThis() Methode wartet, was zu einem TryThis() Warten führt.

Um dies zu beheben, sollte der Code in geändert werden

private async void button1_Click(object sender, EventArgs e)
{
  bool result = await TryThis();
  Trace.TraceInformation("Done with result");
}

Hinweis: Event-Handler sind der einzige Ort, an dem async void verwendet werden sollte (da Sie keine async void Methode erwarten können).

Async / await verbessert die Leistung nur, wenn die Maschine zusätzliche Arbeit ausführen kann

Betrachten Sie den folgenden Code:

public async Task MethodA()
{
     await MethodB();
     // Do other work
}

public async Task MethodB()
{
     await MethodC();
     // Do other work
}

public async Task MethodC()
{
     // Or await some other async work
     await Task.Delay(100);
}

Dies wird nicht besser sein als

public void MethodA()
{
     MethodB();
     // Do other work
}

public void MethodB()
{
     MethodC();
     // Do other work
}

public void MethodC()
{
     Thread.Sleep(100);
}

Der Hauptzweck von async / await besteht darin, der Maschine zusätzliche Arbeit zu ermöglichen, z. B. um dem aufrufenden Thread andere Arbeit zu ermöglichen, während er auf ein Ergebnis einer E / A-Operation wartet. In diesem Fall darf der aufrufende Thread niemals mehr Arbeit erledigen, als es sonst möglich gewesen wäre. Es gibt also keinen Leistungsgewinn, MethodA() Sie einfach MethodA() , MethodB() und MethodC() synchron MethodC() .



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow