2014-08-28 4 views
8

Ich hatte kürzlich eine Situation, in der ich einen ASP.NET-WebAPI-Controller hatte, der innerhalb seiner Aktionsmethode zwei Webanforderungen an einen anderen REST-Service ausführen musste. Ich hatte meinen Code geschrieben Funktionalität sauber in separate Verfahren getrennt zu haben, die ein wenig wie in diesem Beispiel sah:Wie funktioniert die async/await Callchain?

public class FooController : ApiController 
{ 

    public IHttpActionResult Post(string value) 
    { 
     var results = PerformWebRequests(); 
     // Do something else here... 
    } 

    private IEnumerable<string> PerformWebRequests() 
    { 
     var result1 = PerformWebRequest("service1/api/foo"); 
     var result = PerformWebRequest("service2/api/foo"); 

     return new string[] { result1, result2 }; 
    } 

    private string PerformWebRequest(string api) 
    { 
     using (HttpClient client = new HttpClient()) 
     { 
      // Call other web API and return value here... 
     } 
    } 

} 

Weil ich wurde mit HttpClient alle Web-Anfragen hatte async sein. Ich habe noch nie Async/Abwarten verwendet, also fing ich an, naiv die Keywords hinzuzufügen. Zuerst habe ich das async Schlüsselwort der PerformWebRequest(string api) Methode hinzugefügt, aber dann beschwerte sich der Anrufer, dass die PerformWebRequests() Methode async auch sein muss, um await zu verwenden. Also habe ich das gemacht async aber jetzt muss der Aufrufer dieser Methode async auch sein, und so weiter.

Was ich wissen möchte ist, wie weit unten im Kaninchenloch alles markiert sein muss async um einfach zu arbeiten? Sicher würde es einen Punkt geben, an dem etwas synchron laufen muss, wie wird das dann sicher gehandhabt? Ich habe bereits gelesen, dass das Aufrufen von Task.Result eine schlechte Idee ist, weil es Deadlocks verursachen könnte.

+0

Ich debattiere, ob dies als ein Duplikat qualifiziert: http://StackOverflow.com/Questions/9208921/async-on-main-method-of-console-app – spender

+1

Sie könnten dieses tolle Video über Async auf channel9 sehen - http://channel9.msdn.com/events/TechDays/Techdays-2014-the-Netherlands/Async-programming-deep-dive – terrybozzio

+1

Eine weitere hervorragende Ressource ist die Eduasync-Reihe von Blog-Posts von Jon Skeet http: // Codeblog. jonskeet.uk/category/eduasync/ – softveda

Antwort

13

Was ich wissen möchte ist, wie weit unten im Kaninchenloch muss alles Async markiert sein, um einfach zu arbeiten? Sicher würde es einen Punkt kommen, wo etwas synchron

Nein laufen hat, sollte es nicht einen Punkt, wo alles synchron läuft, und das ist, was async geht. Der Satz "async den ganzen Weg" bedeutet eigentlich den ganzen Weg bis der Call-Stack.

Wenn Sie eine Nachricht asynchron verarbeiten, lassen Sie Ihre Nachrichtenschleife Anfragen verarbeiten, während Ihre wirklich asynchrone Methode ausgeführt wird, denn wenn Sie tief in das alte Loch gehen, There is no Thread.

Zum Beispiel, wenn Sie einen Asynchron-Schaltfläche Click-Ereignishandler haben:

private async void Button_Click(object sender, RoutedEventArgs e) 
{ 
    await DoWorkAsync(); 
    // Do more stuff here 
} 

private Task DoWorkAsync() 
{ 
    return Task.Delay(2000); // Fake work. 
} 

Wenn die Schaltfläche geklickt wird, läuft synchron, bis die ersten await schlagen. Einmal getroffen, gibt die Methode die Kontrolle an den Aufrufer zurück, was bedeutet, dass der Button-Event-Handler den UI-Thread freigibt, wodurch die Nachrichtenschleife freigegeben wird, um in der Zwischenzeit weitere Anfragen zu verarbeiten. Das gleiche gilt für Ihre Verwendung von HttpClient. Zum Beispiel, wenn Sie haben:

public async Task<IHttpActionResult> Post(string value) 
{ 
    var results = await PerformWebRequests(); 
    // Do something else here... 
} 

private async Task<IEnumerable<string>> PerformWebRequests() 
{ 
    var result1 = await PerformWebRequestAsync("service1/api/foo"); 
    var result = await PerformWebRequestAsync("service2/api/foo"); 

    return new string[] { result1, result2 }; 
} 

private async string PerformWebRequestAsync(string api) 
{ 
    using (HttpClient client = new HttpClient()) 
    { 
     await client.GetAsync(api); 
    } 

    // More work.. 
} 

Sehen Sie, wie das async Stichwort ging den ganzen Weg zum Hauptverfahren bis die Verarbeitung der POST Anfrage. Während die asynchrone HTTP-Anforderung vom Netzwerkgerätetreiber verarbeitet wird, kehrt der Thread zu ASP.NET ThreadPool zurück und kann zwischenzeitlich weitere Anforderungen verarbeiten.

Eine Konsolenanwendung ist ein Sonderfall, da die Main Methode endet, wenn Sie einen neuen Vordergrund Thread drehen, wird die App beendet.Dort müssen Sie sicherstellen, dass Sie, wenn der einzige Anruf ein Async-Anruf ist, explizit Task.Wait oder Task.Result verwenden müssen. Aber in diesem Fall ist der Standard SynchronizationContext der ThreadPoolSynchronizationContext, wo es keine Chance gibt, einen Deadlock zu verursachen.

Abschliessend async Methoden sollte nicht synchron mit der Spitze des Stapel verarbeitet werden, es sei denn, es ist eine exotische Anwendungsfall (wie etwa eine Konsole App), sollten sie fließen asynchron den gesamten Weg ermöglicht, den Faden zu wenn möglich befreit werden.

+0

Gute Antwort. Als Optimierung könnte man auch dafür sorgen, dass die beiden Aufrufe von 'PerformWebRequestAsync' parallel gemacht und mit 'Task.WhenAll' abgewartet werden können. Dies setzt natürlich voraus, dass das Ergebnis des ersten Anrufs von der Anfrage des zweiten Anrufs nicht benötigt wird. –

+0

@MattJohnson Danke matt, ich dachte daran, wollte aber den OP-Code nicht zu sehr verändern, um ihn in den Kontext der Antwort zu stellen. –

+1

Danke @YuvalItzchakov, das erklärt mir sehr viel, vor allem, wenn es in einer 'Main()' Methode verwendet wird (worüber ich mich auch wunderte). Hört sich für mich an, dass "async/wait" das alles viel einfacher macht, aber es gibt auch Überlegungen zum Design, bevor man es einschaltet - zum Beispiel, wie viel Code man ändern muss, um eine "Async" -Methode hinzuzufügen. –

0

Sie müssen ganz oben auf dem Call-Stack "async" sein, wo Sie eine Nachrichtenschleife erreichen, die alle asynchronen Anfragen verarbeiten kann.