Czy Task.Result jest taki sam jak .GetAwaiter.GetResult ()?


328

Niedawno czytałem jakiś kod, który używa wielu metod asynchronicznych, ale czasem muszę je wykonywać synchronicznie. Kod:

Foo foo = GetFooAsync(...).GetAwaiter().GetResult();

Czy to to samo co

Foo foo = GetFooAsync(...).Result;

8
Z dokumentów GetResult: „Ten typ i jego członkowie są przeznaczone do użytku przez kompilator”. Inna osoba nie powinna go używać.
spędza

32
Nazywa się to „synchronizuj przez asynchronię” i jeśli nie wiesz, w jaki sposób zadanie jest realizowane, może to być naprawdę zły pomysł. Może natychmiast zablokować się w wielu przypadkach (na przykład metoda async/ awaitw MVC)
Marc Gravell


14
W prawdziwym świecie mamy konstruktory, interfejsy „nie czekamy”, które musimy zaimplementować, a metody asynchroniczne są dostępne wszędzie. Z przyjemnością skorzystam z czegoś, co po prostu działa, bez zastanawiania się, dlaczego jest to „niebezpieczne”, „nieużywane” lub „unikać za wszelką cenę”. Za każdym razem, gdy mam do czynienia z asynchronizacją, pojawia się ból głowy.
Larry

Odpowiedzi:


173

Dosyć. Jedna mała różnica: jeśli Taskzawiedzie, GetResult()po prostu wyrzuci wyjątek spowodowany bezpośrednio, a Task.ResultwyrzuciAggregateException . Jaki jest jednak sens używania któregoś z nich, kiedy jest async? 100 razy lepsza opcja to użycie await.

Ponadto nie masz zamiaru używać GetResult(). Jest przeznaczony wyłącznie do użytku kompilatora, a nie dla ciebie. Ale jeśli nie chcesz denerwować AggregateException, skorzystaj z niego.


27
@JayBazuzi Nie, jeśli Twoja platforma do testowania jednostek obsługuje asynchroniczne testy jednostek, co, jak sądzę, robią najnowsze wersje większości frameworków.
svick

15
@JayBazuzi: MSTest, xUnit i NUnit wszystkie async Tasktesty jednostek wsparcia i mają już jakiś czas.
Stephen Cleary

18
przesuwanie z powrotem na 100x - użycie go jest 1000 razy gorsze, jeśli dostosowujesz stary kod, a użycie go w oczekiwaniu wymaga przepisania.
utknął

13
@AlexZhukovskiy: Nie zgadzam się .
Stephen Cleary

15
The 100x better option is to use await.Nienawidzę takich stwierdzeń, gdybym mógł uderzyć awaitprzed nimi, zrobiłbym to. Ale kiedy próbuję zmusić kod asynchroniczny do pracy z kodem niesynchronicznym, jak to, co często zdarza mi się często w Xamarin, ostatecznie muszę używać takich rzeczy ContinueWith, aby nie zakleszczyć interfejsu użytkownika. Edycja: Wiem, że to stare, ale to nie zmniejsza mojej frustracji ze znalezienia odpowiedzi, które twierdzą, że nie ma alternatywy dla sytuacji, w których nie można po prostu użyć await.
Thomas F.

147

Task.GetAwaiter().GetResult()jest korzystniejszy Task.Waiti Task.Resultponieważ propaguje wyjątki zamiast owijając je w AggregateException. Jednak wszystkie trzy metody powodują potencjalne problemy z zakleszczeniem i brakiem puli wątków. Wszystkich należy unikać na korzyść async/await.

Poniższy cytat wyjaśnia dlaczego Task.Waita Task.Resultnie po prostu zawierać zachowanie propagacji wyjątkiem Task.GetAwaiter().GetResult()(ze względu na „bardzo wysokim barze zgodności”).

Jak wspomniałem wcześniej, mamy bardzo wysoki pasek zgodności, dzięki czemu uniknęliśmy łamania zmian. Jako taki Task.Waitzachowuje swoje pierwotne zachowanie polegające na zawsze zawijaniu. Jednak możesz znaleźć się w niektórych zaawansowanych sytuacjach, w których chcesz zachować zachowanie podobne do synchronicznego blokowania zastosowanego przez Task.Wait, ale w którym chcesz, aby oryginalny wyjątek był propagowany jako nieopakowany, a nie zamknięty w pliku AggregateException. Aby to osiągnąć, możesz bezpośrednio celować w oczekującego na zadanie. Kiedy piszesz „ await task;”, kompilator tłumaczy to na użycie Task.GetAwaiter()metody, która zwraca instancję, która ma GetResult()metodę. W przypadku użycia w zadaniu z błędem, GetResult()propaguje oryginalny wyjątek (w ten sposób „ await task;” uzyskuje swoje zachowanie). W ten sposób możesz użyć „task.GetAwaiter().GetResult()”, Jeśli chcesz bezpośrednio wywołać tę logikę propagacji.

https://blogs.msdn.microsoft.com/pfxteam/2011/09/28/task-exception-handling-in-net-4-5/

GetResult” Oznacza „sprawdź zadanie pod kątem błędów”

Ogólnie staram się unikać synchronicznego blokowania asynchronicznego zadania. Istnieje jednak kilka sytuacji, w których naruszam tę wytyczną. W tych rzadkich warunkach moją preferowaną metodą jest GetAwaiter().GetResult()zachowanie wyjątków zadań zamiast zawijania ich w pliku AggregateException.

http://blog.stephencleary.com/2014/12/a-tour-of-task-part-6-results.html


3
Zasadniczo Task.GetAwaiter().GetResult()jest to równoważne z await task. Zakładam, że pierwsza opcja jest używana, gdy metody nie można oznaczyć async(na przykład konstruktorem). Czy to jest poprawne? Jeśli tak, to koliduje z najwyższą odpowiedzią @ It'sNotALie
OlegI

5
@OlegI: Task.GetAwaiter().GetResult()jest bardziej równoważne Task.Waiti Task.Result(w tym sensie, że wszystkie trzy będą blokować synchronicznie i potencjalnie mogą zostać zakleszczone), ale Task.GetAwaiter().GetResult()ma wyjątkowe zachowanie propagacji oczekiwania na zadanie.
Nitin Agarwal,

Czy nie można uniknąć zakleszczeń w tym scenariuszu za pomocą (Zadanie) .ConfigureAwait (false) .GetAwaiter (). GetResult (); ?
Daniel Lorenz,

3
@DanielLorenz: Zobacz następujący cytat: „Korzystanie z ConfigureAwait (false) w celu uniknięcia zakleszczeń jest niebezpieczną praktyką. Trzeba będzie używać ConfigureAwait (false) do każdego oczekiwania w przejściowym zamknięciu wszystkich metod wywoływanych przez kod blokujący, w tym wszystkich trzecich - i kod innej firmy. Używanie ConfigureAwait (false), aby uniknąć impasu, to w najlepszym razie tylko hack). ... lepszym rozwiązaniem jest „Nie blokuj kodu asynchronicznego”. ” - blog.stephencleary.com/2012/07/dont-block-on-async-code.html
Nitin Agarwal

4
Nie rozumiem Task.Wait i Task.Result są zepsute przez projekt? Dlaczego nie są przestarzałe?
osexpert

69

https://github.com/aspnet/Security/issues/59

„Ostatnia uwaga: należy unikać używania Task.Resulti Task.Waitna tyle, na ile to możliwe, ponieważ zawsze zawierają one wewnętrzny wyjątek w AggregateExceptioni zastępują komunikat ogólnym (Wystąpił jeden lub więcej błędów), co utrudnia debugowanie. Nawet jeśli wersja synchroniczna nie powinna nie należy ich używać tak często, dlatego należy rozważyć użycie Task.GetAwaiter().GetResult()zamiast tego ”.


20
Źródło, do którego się tu odwołuje, to ktoś cytujący kogoś innego, bez odniesienia. Rozważ kontekst: widzę, że wiele osób ślepo używa GetAwaiter (). GetResult () wszędzie po przeczytaniu tego.
Jack Ukleja

2
Więc nie powinniśmy go używać?
tofutim

11
Jeśli dwa zadania zakończą się wyjątkiem, stracisz drugie w tym scenariuszu Task.WhenAll(task1, task2).GetAwaiter().GetResult();.
Monsignor,


33

Kolejna różnica polega na tym, że asyncfunkcja zwraca tylko Taskzamiast Task<T>tego nie można użyć

GetFooAsync(...).Result;

Natomiast

GetFooAsync(...).GetAwaiter().GetResult();

nadal działa.

Wiem, że przykładowy kod w pytaniu dotyczy danej sprawy Task<T>, jednak pytanie jest zadawane ogólnie.


1
To nie jest prawda. Sprawdź moje skrzypce, które wykorzystują dokładnie ten konstrukt: dotnetfiddle.net/B4ewH8
wojciech_rak

3
@wojciech_rak W kodzie korzystasz Resultz GetIntAsync()którego powraca Task<int>nie tylko Task. Proponuję ponownie przeczytać moją odpowiedź.
Nuri Tasdemir

1
Masz rację, na początku zrozumiałem, że odpowiadasz, że nie możesz GetFooAsync(...).Result wewnątrz funkcji, która powraca Task. Ma to teraz sens, ponieważ w C # nie ma właściwości void ( Task.Resultjest to właściwość), ale można oczywiście wywołać metodę void.
wojciech_rak

22

Jak już wspomniano, jeśli możesz użyć await. Jeśli potrzebujesz uruchamiać kod synchronicznie, jak wspomniałeś .GetAwaiter().GetResult(), .Resultlub .Wait()istnieje ryzyko impasu, jak wielu powiedziało w komentarzach / odpowiedziach. Ponieważ większość z nas lubi onelinerów, możesz z nich korzystać.Net 4.5<

Pozyskiwanie wartości metodą asynchroniczną:

var result = Task.Run(() => asyncGetValue()).Result;

Synchroniczne wywoływanie metody asynchronicznej

Task.Run(() => asyncMethod()).Wait();

Z powodu użycia nie wystąpią problemy z zakleszczeniem Task.Run.

Źródło:

https://stackoverflow.com/a/32429753/3850405


1

Jeśli zadanie zawiedzie, wyjątek zostanie ponownie wygenerowany, gdy kod kontynuacji wywoła funkcję oczekiwania.GetResult (). Zamiast wywoływać GetResult, moglibyśmy po prostu uzyskać dostęp do właściwości Result zadania. Zaletą wywołania GetResult jest to, że w przypadku niepowodzenia zadania wyjątek jest generowany bezpośrednio, bez owijania w AggregateException, co pozwala na prostsze i czystsze bloki catch.

W przypadku zadań niegenicznych funkcja GetResult () ma nieważną wartość zwracaną. Jego użyteczną funkcją jest zatem wyłącznie odrzucanie wyjątków.

źródło: c # 7.0 w pigułce

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.