Natknąłem się na kilka najlepszych praktyk dotyczących programowania asynchronicznego przy użyciu słów kluczowych async
/ await
słów kluczowych języka C # (jestem nowy w C # 5.0).
Jedna z udzielonych rad była następująca:
Stabilność: poznaj konteksty synchronizacji
... Niektóre konteksty synchronizacji są niewłączane i jednowątkowe. Oznacza to, że w danym czasie można wykonać tylko jedną jednostkę pracy w kontekście. Przykładem tego jest wątek interfejsu użytkownika systemu Windows lub kontekst żądania ASP.NET. W tych jednowątkowych kontekstach synchronizacji łatwo jest się zablokować. Jeśli odrodzisz zadanie z kontekstu jednowątkowego, a następnie zaczekasz na to zadanie w kontekście, Twój oczekujący kod może blokować zadanie w tle.
public ActionResult ActionAsync()
{
// DEADLOCK: this blocks on the async task
var data = GetDataAsync().Result;
return View(data);
}
private async Task<string> GetDataAsync()
{
// a very simple async method
var result = await MyWebService.GetDataAsync();
return result.ToString();
}
Jeśli spróbuję sam go przeanalizować, główny wątek odrodzi się do nowego MyWebService.GetDataAsync();
, ale ponieważ główny wątek tam czeka, czeka na wynik w GetDataAsync().Result
. W międzyczasie powiedzmy, że dane są gotowe. Dlaczego główny wątek nie kontynuuje swojej logiki kontynuacji i zwraca wynik w postaci ciągu GetDataAsync()
?
Czy ktoś może mi wyjaśnić, dlaczego w powyższym przykładzie jest impas? Nie mam pojęcia, na czym polega problem ...
źródło
var data = GetDataAsync().Result;
jest wiersz kodu, który nigdy nie powinien być wykonywany w kontekście, którego nie należy blokować (żądanie UI lub ASP.NET). Nawet jeśli nie ma zakleszczenia, blokuje wątek przez nieokreślony czas. Więc zasadniczo jest to okropny przykład. [Musisz wyjść z wątku UI przed wykonaniem takiego kodu lub użyć goawait
również, jak sugeruje Toni.]Odpowiedzi:
Spójrz na ten przykład , Stephen ma dla ciebie jasną odpowiedź:
Kolejny link, który powinieneś przeczytać: Oczekiwanie, interfejs użytkownika i zakleszczenia! O mój!
źródło
GetDataAsync().Result;
uruchomi się po zakończeniu zadania zwróconego przezGetDataAsync()
, w międzyczasie blokuje wątek interfejsu użytkownikareturn result.ToString()
) jest umieszczana w kolejce do wątku interfejsu użytkownika w celu wykonaniaGetDataAsync()
zakończy się, gdy zostanie uruchomiona jego kolejka do kontynuacjiImpas!
Impas można przełamać zapewnionymi alternatywami, aby uniknąć faktu 1 lub faktu 2.
var data = await GetDataAsync()
, co umożliwia kontynuowanie działania wątku interfejsu użytkownikavar data = Task.Run(GetDataAsync).Result
Use, co spowoduje wysłanie kontynuacji do kontekstu synchronizacji wątku puli wątków. Pozwala toGetDataAsync()
na ukończenie zadania zwróconego przez .Jest to bardzo dobrze wyjaśnione w artykule Stephena Touba , mniej więcej w połowie, gdzie posługuje się przykładem
DelayAsync()
.źródło
var data = Task.Run(GetDataAsync).Result
to dla mnie nowość. Zawsze myślałem, że zewnętrzna strona.Result
będzie łatwo dostępna, gdy tylkoGetDataAsync
zostanie trafiony pierwszy element , więcdata
zawsze będziedefault
. Ciekawy.Właśnie ponownie bawiłem się tym problemem w projekcie ASP.NET MVC. Jeśli chcesz wywołać
async
metody z aPartialView
, nie możesz utworzyćPartialView
async
. Jeśli to zrobisz, dostaniesz wyjątek.Możesz użyć następującego prostego obejścia w scenariuszu, w którym chcesz wywołać
async
metodę z metody synchronizacji:SynchronizationContext
SynchronizationContext
Przykład:
public ActionResult DisplayUserInfo(string userName) { // trick to prevent deadlocks of calling async method // and waiting for on a sync UI thread. var syncContext = SynchronizationContext.Current; SynchronizationContext.SetSynchronizationContext(null); // this is the async call, wait for the result (!) var model = _asyncService.GetUserInfo(Username).Result; // restore the context SynchronizationContext.SetSynchronizationContext(syncContext); return PartialView("_UserInfo", model); }
źródło
Inną ważną kwestią jest to, że nie należy blokować zadań i używać funkcji asynchronicznej do końca, aby zapobiec zakleszczeniom. Wtedy będzie to blokowanie asynchroniczne, a nie synchroniczne.
public async Task<ActionResult> ActionAsync() { var data = await GetDataAsync(); return View(data); } private async Task<string> GetDataAsync() { // a very simple async method var result = await MyWebService.GetDataAsync(); return result.ToString(); }
źródło
Obejście, do którego doszedłem, polega na użyciu
Join
metody rozszerzającej w zadaniu przed zapytaniem o wynik.Kod wygląda następująco:
public ActionResult ActionAsync() { var task = GetDataAsync(); task.Join(); var data = task.Result; return View(data); }
Gdzie metoda łączenia to:
public static class TaskExtensions { public static void Join(this Task task) { var currentDispatcher = Dispatcher.CurrentDispatcher; while (!task.IsCompleted) { // Make the dispatcher allow this thread to work on other things currentDispatcher.Invoke(delegate { }, DispatcherPriority.SystemIdle); } } }
Nie jestem na tyle w domenie, aby zobaczyć wady tego rozwiązania (jeśli występują)
źródło