2010-10-29 26 views
58

Nie widzę różnic między nowymi funkcjami asynchronicznymi C# (i VB), a Task Parallel Library .NET 4.0. Weźmy, na przykład, kod Eric Lippert za from here:W jaki sposób funkcja async-await w C# 5.0 różni się od licencji TPL?

async void ArchiveDocuments(List<Url> urls) { 
    Task archive = null; 
    for(int i = 0; i < urls.Count; ++i) { 
     var document = await FetchAsync(urls[i]); 
     if (archive != null) 
      await archive; 
     archive = ArchiveAsync(document); 
    } 
} 

Wydaje się, że kluczowe await obsługuje dwa różne cele. Pierwsze zdarzenie (FetchAsync) wydaje się oznaczać, "Jeśli ta wartość jest używana później w metodzie i jej zadanie nie jest zakończone, poczekaj, aż zakończy się przed kontynuowaniem." Druga instancja (archive) wydaje się oznaczać, "Jeśli to zadanie nie zostało jeszcze zakończone, poczekaj teraz, aż się zakończy." Jeśli się mylę, popraw mnie.

Czy nie można tak łatwo napisać w ten sposób?

void ArchiveDocuments(List<Url> urls) { 
    for(int i = 0; i < urls.Count; ++i) { 
     var document = FetchAsync(urls[i]);  // removed await 
     if (archive != null) 
      archive.Wait();      // changed to .Wait() 
     archive = ArchiveAsync(document.Result); // added .Result 
    } 
} 

Mam zastąpił pierwszy await z Task.Result gdzie wartość jest rzeczywiście potrzebna, a drugi await z Task.Wait(), gdzie czas oczekiwania jest faktycznie występującej. Funkcjonalność jest już zaimplementowana, a (1) znacznie ściślejsza semantycznie względem tego, co faktycznie dzieje się w kodzie.

Zdaję sobie sprawę, że metoda async została przepisana jako maszyna stanu, podobna do iteratorów, ale nie widzę również korzyści, jakie przynosi. Każdy kod, który wymaga innego wątku do działania (takiego jak pobieranie) nadal będzie wymagał innego wątku, a każdy kod, który nie będzie działał (taki jak odczyt z pliku) może nadal wykorzystywać licencję TPL do pracy tylko z jednym wątkiem.

Oczywiście brakuje mi czegoś wielkiego; Czy ktokolwiek może mi pomóc zrozumieć to trochę lepiej?

+0

także zobaczyć http://stackoverflow.com/questions/3513432/task-parallel-library-replacement-for-backgroundworker ? lq = 1 i http://stackoverflow.com/questions/12414601/async-await-vs-backgroundworker?lq=1 – nawfal

+0

Zobacz ten link, który ma ładne przykłady: http://richnewman.wordpress.com/2012/12/03/tutorial-asynchroniczne programowanie-async-and-await-for-beginners/ – nawfal

Odpowiedz

69

myślę nieporozumienie powstaje tutaj:

Wydaje się, że kluczowe Oczekujcie obsługuje dwa różne cele. Pierwsze wystąpienie (FetchAsync) wydaje się oznaczać, "Jeśli ta wartość jest używana później w metodzie, a jej zadanie nie jest zakończone, poczekaj, aż zakończy się, zanim przejdziesz dalej." Druga instancja (archiwum) zdaje się oznaczać: "Jeśli to zadanie nie zostało jeszcze zakończone, poczekaj, aż to się zakończy." Jeśli się mylę, popraw mnie.

To jest całkowicie niepoprawne. Oba mają to samo znaczenie.

W pierwszym przypadku:

var document = await FetchAsync(urls[i]); 

Co tu się dzieje, jest to, że środowisko wykonawcze mówi: „Zadzwoń FetchAsync, a następnie powrócić aktualny punkt wykonanie do wątku wywołaniem tej metody.” Tu nie ma "czekania" - zamiast tego, wykonanie powraca do kontekstu synchronizacji wywołań, a rzeczy wciąż się kręcą. W pewnym momencie zadanie FetchAsync zakończy się, a w tym momencie ten kod zostanie wznowiony w kontekście synchronizacji wątku wywołującego, a następnie pojawi się następna instrukcja (przypisanie zmiennej dokumentu).

Wykonanie będzie kontynuowane aż do drugiego oczekującego połączenia - w tym czasie to samo się stanie - jeśli Task<T> (archiwum) nie jest kompletne, wykonanie zostanie zwolnione do kontekstu wywołania - w przeciwnym razie archiwum będzie zestaw.

W drugim przypadku rzeczy są bardzo różne - tutaj jawnie blokujesz, co oznacza, że ​​kontekst synchronizacji wywołań nigdy nie będzie miał szansy na wykonanie dowolnego kodu, dopóki cała twoja metoda nie zostanie ukończona. To prawda, że ​​nadal istnieje asynchronia, ale asynchronia jest całkowicie zawarta w tym bloku kodu - w tym wątku nie będzie żadnego kodu poza tym wklejonym kodem, dopóki cały kod nie zostanie ukończony.

0

Wezwanie do FetchAsync() nadal będzie blokować aż zakończy się (o ile oświadczenie w połączeniach await?) Kluczem jest to, że sterowanie jest zwracane do rozmówcy (bo sama metoda ArchiveDocuments jest zadeklarowana jako async). Tak więc wywołujący może z powodzeniem kontynuować przetwarzanie logiki interfejsu użytkownika, reagować na zdarzenia itp.

Po zakończeniu przerywa wywołującemu, aby zakończyć pętlę. Uderza w numer ArchiveAsync() i blokuje, ale prawdopodobnie tylko tworzy nowe zadanie, uruchamia je i zwraca zadanie. Pozwala to na rozpoczęcie drugiej pętli, podczas gdy zadanie jest przetwarzane.

Druga pętla trafia FetchAsync() i blokuje, zwracając kontrolę do osoby dzwoniącej. Po zakończeniu FetchAsync() ponownie przerywa wywołującego, aby kontynuować przetwarzanie. Następnie trafia ona na await archive, która zwraca kontrolę do osoby wywołującej, dopóki nie zakończy się Task utworzona w pętli 1. Po zakończeniu tego zadania wywołujący jest ponownie przerywany, a druga pętla wywołuje ArchiveAsync(), która rozpoczyna zadanie i rozpoczyna pętlę 3, powtarzając ad nauseum.

Klawisz zwraca kontrolę dzwoniącemu podczas wykonywania ciężkich podnośników.

+1

Uwaga: "ciężkie podnośniki" mogą nie być wykonywane. Mogą wcale nie być równoległe. Mogą to być jednostki pracy, które mają być zaplanowane w tym wątku, gdy tylko wątek ten przestanie działać. Widzę * dużo * mieszania asynchronii z paralelizmem i jestem gotów rozwieść ludzi z tego pojęcia; asynchrony często są związane z * nie * tworzeniem większej liczby wątków roboczych. Kluczem jest w rzeczywistości powrót kontroli do osoby dzwoniącej * po * "ciężkich zawodników" zrobili * coś *, aby zapewnić, że ich zadanie zakończy się w pewnym momencie w przyszłości. –

+0

Rozumiem, co mówisz, i zgadzasz się, że "oczekiwana metoda nie musi uruchamiać ani planować niczego w ogóle. To, co skłoniło mnie do wydania tego oświadczenia, to "archive.Wait()". W tym przykładzie kod jest napisany tak, aby czekać na wątek, aby coś od razu wykonać i ukończyć. Moje oświadczenie dotyczyło tylko tego przykładu. –

+0

@JamesKing, w odniesieniu do "_... nie trzeba uruchamiać ani planować niczego w ogóle": to zaplanuje lub zaplanuje s/t, prawda? – bvgheluwe

24

Istnieje ogromna różnica:

Wait() bloki, await nie blokuje.Jeśli uruchomisz asynchroniczną wersję ArchiveDocuments() w swoim wątku GUI, GUI pozostanie responsywny podczas operacji pobierania i archiwizacji. Jeśli używasz wersji TPL z Wait(), twój GUI zostanie zablokowany.

Zauważ, że async udaje się to zrobić bez wprowadzania żadnych wątków - w punkcie await kontrola jest po prostu zwracana do pętli wiadomości. Po zakończeniu oczekiwania na zadanie, pozostała część metody (kontynuacja) zostanie umieszczona w pętli komunikatów, a wątek GUI będzie nadal działał ArchiveDocuments, w którym został przerwany.

4

Problem polega na tym, że podpis ArchiveDocuments wprowadza w błąd. Ma wyraźny zwrot w wysokości: void, ale tak naprawdę zwrot wynosi Task. Dla mnie pustka oznacza synchroniczne, ponieważ nie ma sposobu, aby "czekać", aż to się skończy. Rozważ alternatywną sygnaturę funkcji.

async Task ArchiveDocuments(List<Url> urls) { 
    ... 
} 

Dla mnie, gdy jest napisane w ten sposób różnica jest bardziej oczywista. Funkcja ArchiveDocuments nie jest funkcją, która kończy się synchronicznie, ale zakończy się później.

+2

Kod ten miałby dużo więcej sensu, gdyby "archiwum" było członkiem lub zmienną statyczną i nie zostało zdefiniowane w metodzie ... Powiedziawszy to, funkcje asynchroniczne, które powracają, są całkowicie poprawne i akceptowalne oraz mają "ogień i zapomnij" "co oznacza, zgodnie z dokumentacją specyfikacji. –

+0

Czy mówisz, że wywołanie funkcji 'ArchiveDocuments()' powinno wyglądać tak, jak 'Task task = ArchiveDocuments (Lista adresów URL: Lista adresów: );'? To nie wydaje się właściwe ... i jeśli dobrze rozumiem, osoba dzwoniąca w ogóle nie czeka. W rzeczywistości, jeśli dba o wynik 'ArchiveDocuments()', cały scenariusz rozpada się i nie działa. –

+1

@Reed, zgadzam się, że są one rzeczywiście prawidłowe i poprawne. Po prostu uważam, że jest to bardzo mylące i trochę presumpstious, ponieważ może nie chcę o tym zapomnieć;) – JaredPar

5

Możliwość zamiany przepływu programu sterowania na automat stanów jest tym, co sprawia, że ​​nowe słowa kluczowe są interesujące. Pomyśl o tym, jako o wartości, a nie o wartości.

Zapoznaj się z this Channel 9 video Andersa mówiącego o nowej funkcji.

23

Anders sprowadził to do bardzo zwięzłej odpowiedzi w wywiadzie dla Channel 9 Live, który zrobił. Gorąco polecam

Nowe słowa kluczowe Async i czekaj pozwalają na orkiestrować współbieżność w swoich aplikacjach. W rzeczywistości nie wprowadzają żadnej współbieżności do aplikacji.

OC a dokładniej Zadanie jest jeden sposób można użyć do faktycznie wykonywać operacje jednocześnie. Nowe asynchroniczne i oczekujące słowo kluczowe pozwala na komponowanie tych współbieżnych operacji w sposób "synchroniczny" lub "liniowy".

Możesz więc napisać liniowy strumień sterowania w swoich programach, podczas gdy faktyczne obliczenia mogą, ale nie muszą, występować jednocześnie. Gdy obliczenia odbywają się jednocześnie, czekaj i asynchronicznie pozwalają na wykonanie tych operacji.

+2

To właściwie nic nie mówi, prawda? Pozwolę sobie powtórzyć: jak to odpowiada na postawione pytanie? –

+13

Pytanie brzmi: "Jak funkcja async-await C# 5.0 różni się od OC?" Moja odpowiedź jest odpowiednia dla tego pytania IMO –