Mam async
metodę:
public async Task<string> GenerateCodeAsync()
{
string code = await GenerateCodeService.GenerateCodeAsync();
return code;
}
Muszę wywołać tę metodę z metody synchronicznej.
Jak mogę to zrobić bez konieczności duplikowania GenerateCodeAsync
metody, aby działała synchronicznie?
Aktualizacja
Jednak nie znaleziono rozsądnego rozwiązania.
Widzę jednak, że HttpClient
już implementuje ten wzorzec
using (HttpClient client = new HttpClient())
{
// async
HttpResponseMessage responseAsync = await client.GetAsync(url);
// sync
HttpResponseMessage responseSync = client.GetAsync(url).Result;
}
Odpowiedzi:
Możesz uzyskać dostęp do
Result
właściwości zadania, co spowoduje blokowanie twojego wątku, dopóki wynik nie będzie dostępny:Uwaga: W niektórych przypadkach może to prowadzić do impasu: Twoje wezwanie do
Result
zablokowania głównego wątku, uniemożliwiając w ten sposób pozostałą część kodu asynchronicznego do wykonania. Masz następujące opcje, aby upewnić się, że tak się nie stanie:.ConfigureAwait(false)
do swojej biblioteki metody lubjawnie uruchom swoją metodę asynchroniczną w wątku puli wątków i poczekaj na jej zakończenie:
To nie znaczy, że należy po prostu bezmyślnie dodać
.ConfigureAwait(false)
po wszystkich połączeń asynchronicznych! Aby uzyskać szczegółową analizę, dlaczego i kiedy powinieneś używać.ConfigureAwait(false)
, zobacz następujący post na blogu:źródło
result
grozi impasem, to kiedy można bezpiecznie uzyskać wynik? Czy każde połączenie asynchroniczne wymagaTask.Run
lubConfigureAwait(false)
?AspNetSynchronizationContext.Post
szereguje asynchroniczne kontynuacje:Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action)); _lastScheduledTask = newTask;
Task.Run
aby zachować bezpieczeństwo. Lub użyj czegoś takiego,WithNoContext
aby zmniejszyć zbędne przełączanie wątków..Result
może nadal być zakleszczone, jeśli osoba dzwoniąca znajduje się w samej puli wątków. Weźmy scenariusz, w którym pula wątków ma rozmiar 32, a zadania 32 są uruchomione iWait()/Result
oczekują na 33. zadanie, które ma zostać zaplanowane, i które ma zostać uruchomione w jednym z oczekujących wątków.Powinieneś dostać awaiter (
GetAwaiter()
) i zakończyć oczekiwanie na zakończenie asynchronicznego zadania (GetResult()
).źródło
Task.GetAwaiter
: Ta metoda jest przeznaczona raczej do użytku kompilatora niż do użycia w kodzie aplikacji.Powinieneś być w stanie to zrobić za pomocą delegatów, wyrażenia lambda
źródło
Jest to możliwe przy pomocy
GenerateCodeAsync().Result
lubGenerateCodeAsync().Wait()
, jak sugeruje inna odpowiedź. Spowodowałoby to zablokowanie bieżącego wątku, dopókiGenerateCodeAsync
nie zostanie zakończone.Twoje pytanie jest jednak oznaczone tagiem asp.net, a także zostawiłeś komentarz:
Chodzi mi o to, że nie powinieneś blokować metody asynchronicznej w ASP.NET. Spowoduje to zmniejszenie skalowalności Twojej aplikacji internetowej i może spowodować impas (gdy
await
kontynuacja wewnątrzGenerateCodeAsync
jest wysyłana doAspNetSynchronizationContext
). UżycieTask.Run(...).Result
do odciążenia wątku puli, a następnie bloku jeszcze bardziej zaszkodzi skalowalności, ponieważ powoduje +1 wątek więcej do przetworzenia danego żądania HTTP.ASP.NET posiada wbudowane wsparcie dla metod asynchronicznych, poprzez asynchronicznych kontrolerów (w ASP.NET MVC i Web API) lub bezpośrednio
AsyncManager
iPageAsyncTask
w klasycznej ASP.NET. Powinieneś go użyć. Aby uzyskać więcej informacji, sprawdź tę odpowiedź .źródło
SaveChanges()
metodęDbContext
i tutaj wywołuję metody asynchroniczne, więc niestety kontroler asynchroniczny nie pomoże mi w tej sytuacjiSaveChangesAsync
iSaveChanges
tylko upewnić się, że nie zostaną wywołane oba w tym samym projekcie ASP.NET..NET MVC
filtry obsługują kod asynchronicznyIAuthorizationFilter
, więc nie mogę używać goasync
do końcaMicrosoft Identity ma metody rozszerzeń, które wywołują metody asynchroniczne synchronicznie. Na przykład istnieje metoda GenerateUserIdentityAsync () i równa CreateIdentity ()
Jeśli spojrzysz na UserManagerExtensions.CreateIdentity (), wygląda to tak:
Teraz pozwala zobaczyć, co robi AsyncHelper.RunSync
To jest opakowanie dla metody asynchronicznej. I proszę nie czytaj danych z Rezultatu - potencjalnie zablokuje Twój kod w ASP.
Jest jeszcze inny sposób - co jest dla mnie podejrzane, ale można to również rozważyć
źródło
Aby zapobiec zakleszczeniom, zawsze staram się używać,
Task.Run()
gdy muszę synchronicznie wywoływać metodę asynchroniczną, o której wspomina @Heinzi.Jednak metoda musi zostać zmodyfikowana, jeśli metoda asynchroniczna wykorzystuje parametry. Na przykład
Task.Run(GenerateCodeAsync("test")).Result
podaje błąd:Zamiast tego można to tak nazwać:
źródło
Większość odpowiedzi w tym wątku jest albo złożona, albo spowoduje impas.
Poniższa metoda jest prosta i pozwoli uniknąć impasu, ponieważ czekamy na zakończenie zadania i dopiero wtedy otrzymujemy jego wynik
Ponadto znajduje się odniesienie do artykułu MSDN, który mówi o dokładnie tej samej rzeczy - https://blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result- w głównym kontekście /
źródło
Wolę podejście nieblokujące:
źródło
Cóż, używam tego podejścia:
źródło
Innym sposobem może być czekanie na zakończenie zadania:
źródło
EDYTOWAĆ:
Task ma metodę Wait, Task.Wait (), która czeka na „obietnicę” do rozwiązania, a następnie kontynuuje, czyniąc ją synchroniczną. przykład:
źródło
Jeśli masz metodę asynchroniczną o nazwie „ RefreshList ”, możesz wywołać tę metodę asynchroniczną z metody innej niż asynchronicznej, jak poniżej.
źródło