Próbuję zrozumieć, że async czeka w najprostszej formie. Chcę stworzyć bardzo prostą metodę, która doda dwie liczby na potrzeby tego przykładu, oczywiście, to wcale nie jest czas przetwarzania, to tylko kwestia sformułowania tutaj przykładu.
Przykład 1
private async Task DoWork1Async()
{
int result = 1 + 2;
}
Przykład 2
private async Task DoWork2Async()
{
Task.Run( () =>
{
int result = 1 + 2;
});
}
Jeśli czekam DoWork1Async()
, czy kod będzie działał synchronicznie czy asynchronicznie?
Czy muszę owinąć kod synchronizacji, Task.Run
aby metoda była oczekiwana ORAZ asynchroniczna, aby nie blokować wątku interfejsu użytkownika?
Próbuję dowiedzieć się, czy moja metoda jest a Task
czy zwraca, Task<T>
czy muszę owinąć kod, Task.Run
aby był asynchroniczny.
Głupie pytanie, jestem pewien, ale widzę przykłady w sieci, gdzie ludzie czekają na kod, który nie ma nic asynchronicznego w środku i nie jest owinięty w Task.Run
lub StartNew
.
źródło
Odpowiedzi:
Najpierw wyjaśnijmy trochę terminologii: „asynchroniczny” (
async
) oznacza, że może odzyskać kontrolę nad wątkiem wywołującym przed jego uruchomieniem. Wasync
metodzie te punkty „ustępowania” sąawait
wyrażeniami.Jest to bardzo różni się od terminu „asynchroniczny”, ponieważ (błędnie) używany w dokumentacji MSDN od lat oznacza „wykonuje się w wątku w tle”.
Dalsze mylenie problemu
async
jest zupełnie inne niż „oczekiwanie”; istniejąasync
metody, których typy zwracane są nieoczekiwane, i wiele metod zwracających oczekiwane typy, które nie są oczekiwaneasync
.Dość o tym, czym nie są ; oto czym one są :
async
kluczowe pozwala na metodę asynchroniczną (tzn. Pozwala naawait
wyrażenia).async
metody mogą zwrócićTask
,Task<T>
lub (jeśli musisz)void
.Task
iTask<T>
.Jeśli więc przeformułujemy twoje pytanie na „jak mogę uruchomić operację na wątku w tle w sposób, w jaki jest to oczekiwane”, odpowiedzią jest użycie
Task.Run
:(Ale ten wzór jest złym podejściem; patrz poniżej).
Ale jeśli twoje pytanie brzmi: „jak utworzyć
async
metodę, która może ustępować swojemu programowi wywołującemu zamiast blokować”, odpowiedzią jest zadeklarowanie metodyasync
i użycieawait
jej punktów „ustępujących”:Zatem podstawowym wzorem rzeczy jest
async
zależność kodu od „oczekiwań” w jegoawait
wyrażeniach. Te „oczekiwane” mogą być innymiasync
metodami lub zwykłymi metodami zwracającymi oczekiwane. Zwykłe metody powracającyTask
/Task<T>
można użyćTask.Run
do wykonania kodu w wątku tła, lub (częściej) mogą używaćTaskCompletionSource<T>
lub jeden z jego (skrótyTaskFactory.FromAsync
,Task.FromResult
itp). I nie polecam owijania całą metodę inTask.Run
; metody synchroniczne powinny mieć sygnatury synchroniczne i konsument powinien pozostawić pytanie, czy ma być zawinięty wTask.Run
:Mam
async
/await
intro na moim blogu; na końcu są dobre zasoby uzupełniające. Dokumenty MSDN dlaasync
są również niezwykle dobre.źródło
async
Metody muszą powrócićTask
,Task<T>
albovoid
.Task
iTask<T>
są oczekiwane;void
nie jest.async void
kompiluje się podpis metody, to po prostu straszny pomysł, gdy tracisz wskaźnik do zadania asynchronicznegovoid
nie jest to oczekiwane.Task.Run
(jakDoWorkAsync
w tej odpowiedzi). KorzystanieTask.Run
aby nazwać metodę z kontekstu UI jest odpowiednia (jakDoVariousThingsFromTheUIThreadAsync
).Task.Run
aby wywołać metodę, ale jeśli jest w niejTask.Run
cały (lub prawie cały) kod metody, to jest to anty-wzorzec - po prostu utrzymuj synchronizację tej metody i przejdźTask.Run
wyżej.Jedną z najważniejszych rzeczy do zapamiętania podczas dekorowania metody za pomocą asynchronii jest to, że przynajmniej jeden operator oczekuje w metodzie. W twoim przykładzie przetłumaczyłbym to, jak pokazano poniżej, przy użyciu TaskCompletionSource .
źródło
Gdy używasz Task.Run do uruchomienia metody, Task pobiera wątek z puli wątków, aby uruchomić tę metodę. Tak więc z punktu widzenia wątku interfejsu użytkownika jest on „asynchroniczny”, ponieważ nie blokuje wątku interfejsu użytkownika. Jest to przydatne w przypadku aplikacji komputerowych, ponieważ zwykle nie potrzebujesz wielu wątków, aby zająć się interakcjami użytkownika.
Jednak w przypadku aplikacji WWW każde żądanie jest obsługiwane przez wątek puli wątków, a zatem liczbę aktywnych żądań można zwiększyć, zapisując takie wątki. Często używane wątki puli wątków do symulacji działania asynchronicznego nie są skalowalne dla aplikacji internetowych.
True Async niekoniecznie wymaga użycia wątku do operacji we / wy, takich jak dostęp do pliku / bazy danych itp. Możesz to przeczytać, aby zrozumieć, dlaczego operacja we / wy nie potrzebuje wątków. http://blog.stephencleary.com/2013/11/there-is-no-thread.html
W prostym przykładzie jest to wyłącznie obliczenie związane z procesorem, więc użycie Task.Run jest w porządku.
źródło
I should NOT wrap the synchronous call in Task.Run()
to prawda. Jeśli to zrobisz, po prostu zamieniasz wątki. tzn. odblokowujesz początkowy wątek żądania, ale pobierasz inny wątek z puli wątków, który mógł zostać wykorzystany do przetworzenia innego żądania. Jedynym rezultatem jest narzut przełączania kontekstu, gdy połączenie jest zakończone dla absolutnie zerowego wzmocnienia