Czy istnieje jakikolwiek scenariusz, w którym pisanie metodę tak:
public async Task<SomeResult> DoSomethingAsync()
{
// Some synchronous code might or might not be here... //
return await DoAnotherThingAsync();
}
zamiast tego:
public Task<SomeResult> DoSomethingAsync()
{
// Some synchronous code might or might not be here... //
return DoAnotherThingAsync();
}
miałoby sens?
Po co używać return await
konstruktora, skoro można bezpośrednio powrócić Task<T>
z wewnętrznego DoAnotherThingAsync()
wywołania?
Widzę kod return await
w tak wielu miejscach, myślę, że mogłem coś przeoczyć. Ale o ile rozumiem, nieużywanie słów kluczowych async / czekaj w tym przypadku i bezpośrednie zwrócenie zadania byłoby funkcjonalnie równoważne. Po co dodawać dodatkowy narzut dodatkowej await
warstwy?
c#
.net
.net-4.5
async-await
TX_
źródło
źródło
Odpowiedzi:
W jednym przypadku podstępne gdy
return
w normalnym sposobem ireturn await
wasync
metodzie zachowują się inaczej: w połączeniu zusing
(lub, bardziej ogólnie, każdyreturn await
wtry
bloku).Rozważ te dwie wersje metody:
Pierwsza metoda przedmiot jak tylko powróci metodzie, która prawdopodobnie na długo zanim faktycznie kończy. Oznacza to, że pierwsza wersja jest prawdopodobnie wadliwa (ponieważ zostanie usunięta zbyt wcześnie), podczas gdy druga wersja będzie działać poprawnie.
Dispose()
Foo
DoAnotherThingAsync()
Foo
źródło
foo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose());
Dispose()
zwracavoid
. Potrzebujesz czegoś takiegoreturn foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; });
. Ale nie wiem, dlaczego miałbyś to zrobić, skoro możesz skorzystać z drugiej opcji.{ var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; }
. Przypadek użycia jest dość prosty: jeśli korzystasz z .NET 4.0 (jak większość), możesz nadal pisać kod asynchroniczny w ten sposób, który będzie działał ładnie z aplikacji 4.5.Foo
dopiero po zakończeniu zwrotuTask
, co mi się nie podoba, ponieważ niepotrzebnie wprowadza współbieżność.Jeśli nie potrzebujesz
async
(tzn. Możesz zwrócićTask
bezpośrednio), nie używajasync
.Jest kilka sytuacji, w których
return await
jest to przydatne, na przykład jeśli masz do wykonania dwie operacje asynchroniczne:Więcej informacji na temat
async
wydajności można znaleźć w artykule MSDN Stephen Toub i wideo na ten temat.Aktualizacja: Napisałem post na blogu, który zawiera dużo więcej szczegółów.
źródło
await
jest to przydatne w drugim przypadku? Dlaczego niereturn SecondAwait(intermediate);
?return SecondAwait(intermediate);
osiągnąłby również celu w tym przypadku? Myślę, żereturn await
tutaj również jest zbędny ...await
w pierwszym wierszu, musisz użyć go również w drugim wierszu.async
wersja zużywa mniej zasobów (wątków) niż wersja synchroniczna.SecondAwait
jest „ciąg, komunikat o błędzie brzmi:„ CS4016: Ponieważ jest to metoda asynchroniczna, wyrażenie zwrotne musi być typu „ciąg”, a nie „Zadanie <ciąg>”.Jedynym powodem, dla którego chcesz to zrobić, jest to, że istnieje jakiś inny
await
kod we wcześniejszym kodzie lub jeśli w jakiś sposób manipulujesz wynikiem przed jego zwróceniem. Innym sposobem, w jaki może się to zdarzyć, jesttry/catch
zmiana sposobu obsługi wyjątków. Jeśli nie robisz nic z tego, masz rację, nie ma powodu, aby dodawać narzut związany z tworzeniem metodyasync
.źródło
return await
to konieczne (zamiast po prostu zwracać zadanie wywołania dziecka), nawet jeśli we wcześniejszym kodzie jest jeszcze coś innego . Czy możesz podać wyjaśnienie?async
to jak czekasz na pierwsze zadanie? Musisz oznaczyć metodę takasync
, jakbyś chciał użyć jakichkolwiek oczekujących. Jeśli metoda jest oznaczona jakoasync
i maszawait
wcześniejszy kod, musisz wykonaćawait
drugą operację asynchroniczną, aby była poprawnego typu. Jeśli właśnie go usunąłeśawait
, kompilacja nie byłaby możliwa, ponieważ zwracana wartość nie byłaby odpowiedniego typu. Ponieważ metoda jestasync
wynikiem, wynik jest zawsze zawinięty w zadanie.async
metodzie nie zwrócisz zadania, zwrócisz wynik zadania, które zostanie następnie zapakowane.Task<Type>
jawnie, podczas gdyasync
dyktuje powrótType
(w który zmieniłby się sam kompilatorTask<Type>
).async
jest tylko cukrem syntaktycznym do jawnego łączenia kontynuacji. Nie musiszasync
nic robić, ale w przypadku niemal każdej trywialnej operacji asynchronicznej znacznie łatwiej jest pracować. Na przykład podany kod nie propaguje błędów tak, jak tego oczekujesz, a robienie tego poprawnie nawet w bardziej skomplikowanych sytuacjach staje się znacznie trudniejsze. Chociaż nigdy nie potrzebujeszasync
, sytuacje, które opisuję, są wartości dodanej do korzystania z nich.Innym przypadkiem, który może wymagać oczekiwania na wynik, jest ten:
W tym przypadku,
GetIFooAsync()
należy poczekać na wynik,GetFooAsync
ponieważ typT
jest różny dla obu metod iTask<Foo>
nie można go bezpośrednio przypisaćTask<IFoo>
. Ale jeśli czekasz na wynik, staje się to,Foo
co można bezpośrednio przypisaćIFoo
. Następnie metoda asynchroniczna po prostu ponownie pakuje wynik wewnątrzTask<IFoo>
i od razu.źródło
Task<>
jest niezmienność.Uczynienie asynchronicznej prostej metody „thunk” tworzy w pamięci maszynę stanu asynchronicznego, a nie asynchroniczną. Chociaż może to często wskazywać na używanie wersji niesynchronicznej, ponieważ jest bardziej wydajna (co jest prawdą), oznacza to również, że w przypadku zawieszenia nie ma dowodów na to, że ta metoda jest zaangażowana w „stos powrotu / kontynuacji” co czasami utrudnia zrozumienie zawieszenia.
Więc tak, gdy perf nie jest krytyczny (a zwykle nie jest), wyrzucę asynchronię na wszystkie te zwarte metody, dzięki czemu będę mieć maszynę stanu asynchronicznego, która pomoże mi zdiagnozować później, a także, aby upewnić się, że jeśli te metody thunk ewoluują z czasem, z pewnością zwrócą błędne zadania zamiast rzucać.
źródło
Jeśli nie użyjesz return, możesz zepsuć ślad stosu podczas debugowania lub gdy jest on drukowany w dziennikach wyjątków.
Po zwróceniu zadania metoda spełniła swój cel i nie znajduje się na stosie wywołań. Podczas używania
return await
pozostawiasz go na stosie połączeń.Na przykład:
Stos wywołań przy użyciu Oczekiwanie: Oczekiwanie na zadanie od B => B Oczekiwanie na zadanie od C
Stos wywołań, gdy nie jest używany, oczekuje: oczekuje na zadanie z C, które B zwróciło.
źródło
To mnie też myli i wydaje mi się, że poprzednie odpowiedzi pomijały twoje aktualne pytanie:
Cóż, czasami tak naprawdę chcesz
Task<SomeType>
, ale przez większość czasu naprawdę chcesz instancjęSomeType
, czyli wynik z zadania.Z Twojego kodu:
Osoba niezaznajomiona ze składnią (na przykład ja) może pomyśleć, że ta metoda powinna zwrócić a
Task<SomeResult>
, ale ponieważ jest oznaczonaasync
, oznacza to, że jej rzeczywistym typem zwrotu jestSomeResult
. Jeśli po prostu użyjeszreturn foo.DoAnotherThingAsync()
, zwrócisz zadanie, którego nie można skompilować. Prawidłowym sposobem jest zwrócenie wyniku zadania, więcreturn await
.źródło
var task = DoSomethingAsync();
dałbyś zadanie, a nieT
async/await
. O ile mi wiadomoTask task = DoSomethingAsync()
, pókiSomething something = await DoSomethingAsync()
zarówno do pracy. Pierwszy daje właściwe zadanie, a drugi, ze względu naawait
słowo kluczowe, daje wynik zadania po jego zakończeniu. Mógłbym na przykład miećTask task = DoSomethingAsync(); Something something = await task;
.