2013-02-15 13 views
37

Dokumentacja MSDN wygląda na to, że async i await są odpowiednie dla zadań związanych z IO, podczas gdy Task.Run powinny być używane do zadań związanych z procesorem.Czy asynchronizacja/oczekiwanie jest odpowiednia dla metod, które są powiązane z IO i procesorem?

Pracuję nad aplikacją, która wykonuje żądania HTTP w celu pobrania dokumentów HTML, które następnie analizuje. Mam metodę, która wygląda tak:

public async Task<HtmlDocument> LoadPage(Uri address) 
{ 
    using (var httpResponse = await new HttpClient().GetAsync(address)) //IO-bound 
    using (var responseContent = httpResponse.Content) 
    using (var contentStream = await responseContent.ReadAsStreamAsync()) 
     return await Task.Run(() => LoadHtmlDocument(contentStream)); //CPU-bound 
} 

Jest to dobre i odpowiednie wykorzystanie async i await, czy jestem nadmiernie go używać?

Odpowiedz

31

istnieją już dwie dobre odpowiedzi, ale dodać moje 0.02 ...

Jeśli mówisz spożywania asynchronicznych operacji, async/await prace doskonale zarówno dla I/O-związany i czynności znacznie obciążające procesor granica.

myślę docs MSDN mają niewielki skos ku produkującej asynchronicznych operacji, w tym przypadku użytkownik chce używać TaskCompletionSource (lub podobny) na I/O-związany i Task.Run (lub podobny) dla CPU-bound .Po utworzeniu początkowego opakowania Task najlepiej jest go przetworzyć pod warunkiem przez async i await.

Dla konkretnego przykładu, to naprawdę sprowadza się do tego, ile czasu zajmie LoadHtmlDocument. Jeśli usuniesz Task.Run, wykonasz go w tym samym kontekście, który wywołuje LoadPage (prawdopodobnie w wątku UI). Windows 8 Wytyczne określają, że wszelkie czynności mające więcej niż 50ms należy async ... pamiętając, że 50ms na komputerze deweloperskim mogą być już na komputerze klienta ...

więc jeśli można zagwarantować, że będzie LoadHtmlDocument trwać mniej niż 50ms, wystarczy wykonać go bezpośrednio:

public async Task<HtmlDocument> LoadPage(Uri address) 
{ 
    using (var httpResponse = await new HttpClient().GetAsync(address)) //IO-bound 
    using (var responseContent = httpResponse.Content) 
    using (var contentStream = await responseContent.ReadAsStreamAsync()) //IO-bound 
    return LoadHtmlDocument(contentStream); //CPU-bound 
} 

jednak polecam ConfigureAwait jak @svick wymienić:

public async Task<HtmlDocument> LoadPage(Uri address) 
{ 
    using (var httpResponse = await new HttpClient().GetAsync(address) 
     .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound 
    using (var responseContent = httpResponse.Content) 
    using (var contentStream = await responseContent.ReadAsStreamAsync() 
     .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound 
    return LoadHtmlDocument(contentStream); //CPU-bound 
} 

z ConfigureAwait, jeśli t Żądanie HTTP nie kończy się natychmiast (synchronicznie), wówczas spowoduje to (w tym przypadku) wykonanie LoadHtmlDocument w wątku puli wątków bez wyraźnego wywołania Task.Run.

Jeśli interesuje Cię wydajność async na tym poziomie, zapoznaj się z video Stephena Touba i MSDN article na ten temat. Ma mnóstwo przydatnych informacji.

12

Właściwe jest, aby każda operacja, która jest asynchroniczna (tj. Jest reprezentowana przez Task).

Najważniejsze jest to, że w przypadku operacji we/wy, gdy tylko jest to możliwe, należy użyć dostarczonej metody, która jest w swoim rdzeniu asynchroniczna, zamiast używać Task.Run w synchronicznej metodzie blokowania. Jeśli blokujesz wątek (nawet wątek puli wątków) podczas wykonywania operacji wejścia/wyjścia, nie wykorzystujesz prawdziwej mocy modelu await.

Po utworzeniu Task, która reprezentuje operację, nie obchodzi już, czy jest to procesor lub granica We/Wy. Dla dzwoniącego jest to tylko pewna operacja asynchroniczna, która musi być await -ed.

17

Jest kilka rzeczy do rozważenia:

  • W aplikacji GUI, jak chcesz trochę kodu, jak to możliwe do wykonania w wątku UI. W takim przypadku dobrym pomysłem jest odciążenie operacji związanej z procesorem na inny wątek przy użyciu Task.Run(). Chociaż użytkownicy kodu mogą to zrobić samodzielnie, jeśli chcą.
  • W aplikacji podobnej do ASP.NET nie ma wątku interfejsu użytkownika, a wszystko, na czym zależy, to wydajność. W takim przypadku istnieje pewne obciążenie związane z używaniem Task.Run() zamiast bezpośredniego uruchamiania kodu, ale nie powinno to być znaczące, jeśli operacja zajmuje trochę czasu. (Wracając do kontekstu synchronizacji, jest jeszcze jeden powód, dla którego powinieneś używać ConfigureAwait(false) dla większości kodów await s).
  • Jeśli twoja metoda jest asynchroniczna (która BTW powinna być również odzwierciedlona w nazwa metody, a nie tylko jej typ zwracany), ludzie będą oczekiwać, że nie zablokuje ona wątku kontekstu synchronizacji, nawet w przypadku pracy związanej z procesorem.

Ważenie tego, myślę, że używanie await Task.Run() jest właściwym wyborem. Ma pewne obciążenie, ale także pewne zalety, które mogą być znaczące.

Powiązane problemy