Rozmycie linii między funkcjami asynchronicznymi i regularnymi w C # 5.0

10

Ostatnio wydaje mi się, że nie mam dość niesamowitego wzoru asynchronicznego oczekiwania w C # 5.0. Gdzie byłeś całe moje życie?

Jestem absolutnie podekscytowana prostą składnią, ale mam jedną małą trudność. Mój problem polega na tym, że funkcje asynchroniczne mają zupełnie inną deklarację niż funkcje zwykłe. Ponieważ tylko inne funkcje asynchroniczne mogą oczekiwać na inne funkcje asynchroniczne, kiedy próbuję przenieść asynchroniczny stary kod blokujący, mam efekt domina funkcji, które muszę przekonwertować.

Ludzie nazywają to infestacją zombie . Kiedy async ugryzie Cię w kodzie, będzie coraz większy. Proces przenoszenia nie jest trudny, po prostu wrzuca asyncdeklarację i zawija wartość zwracaną Task<>. Ale denerwujące jest robienie tego w kółko podczas przenoszenia starego kodu synchronicznego.

Wydaje mi się, że byłoby znacznie bardziej naturalne, gdyby oba typy funkcji (asynchroniczna i zwykła stara synchronizacja) miały dokładnie tę samą składnię. W takim przypadku przeniesienie zajmie zero wysiłku, a ja bezboleśnie mogę przełączać się między tymi dwiema formami.

Myślę, że to może zadziałać, jeśli będziemy przestrzegać następujących zasad:

  1. Funkcje asynchroniczne nie będą już wymagać asyncdeklaracji. Ich typy zwrotów nie musiałyby być zawijane Task<>. Kompilator sam zidentyfikuje funkcję asynchroniczną podczas kompilacji i automatycznie zapakuje zadanie <> w razie potrzeby.

  2. Nigdy więcej wywołań ognia i zapomnij o funkcjach asynchronicznych. Jeśli chcesz wywołać funkcję asynchroniczną, musisz na nią poczekać. I tak prawie nie używam ognia i zapomnienia, a wszystkie przykłady szalonych wyścigów lub impasów zawsze wydają się na nich oparte. Myślę, że są one zbyt mylące i „poza dotykiem” z synchronicznym sposobem myślenia, który próbujemy wykorzystać.

  3. Jeśli naprawdę nie możesz żyć bez ognia i zapomnienia, będzie to wymagało specjalnej składni. W każdym razie nie będzie to częścią prostej zunifikowanej składni, o której mówię.

  4. Jedynym słowem kluczowym potrzebnym do oznaczenia połączenia asynchronicznego jest await. Jeśli czekasz, połączenie jest asynchroniczne. Jeśli tego nie zrobisz, połączenie jest po prostu stare synchroniczne (pamiętaj, że nie mamy już ognia i zapomnij).

  5. Kompilator automatycznie rozpozna funkcje asynchroniczne (ponieważ nie mają już specjalnej deklaracji). Zasada 4 sprawia, że ​​jest to bardzo proste - jeśli funkcja zawiera awaitwywołanie, jest asynchroniczna.

Czy to może zadziałać? czy coś mi brakuje? Ta zunifikowana składnia jest znacznie bardziej płynna i może całkowicie rozwiązać problem zombie.

Kilka przykładów:

// assume this is an async function (has await calls inside)
int CalcRemoteBalanceAsync() { ... }

// assume this is a regular sync function (has no await calls inside)
int CalcRemoteBalance() { ... }

// now let's try all combinations and see what they do:

// this is the common synchronous case - it will block
int b1 = CalcRemoteBalance();

// this is the common asynchronous case - it will not block
int b2 = await CalcRemoteBalanceAsync();

// choosing to call an asynchronous function in a synchronous manner - it will block
// this syntax was used previously for async fire-and-forget, but now it's just synchronous
int b3 = CalcRemoteBalanceAsync();

// strange combination - it will block since it's calling a synchronous function
// it should probably show a compilation warning though
int b4 = await CalcRemoteBalance();

Uwaga: jest to kontynuacja interesującej powiązanej dyskusji w SO

talkol
źródło
3
Zawsze czekasz na swoje operacje asynchroniczne? Powiedz mi, że nie robisz tego natychmiast po ich zwolnieniu ...
Jimmy Hoffa,
1
Jedną z wielkich zalet asynchronizacji jest to, że nie musisz od awaitrazu. Możesz zrobić coś takiego var task = FooAsync(); Bar(); await task;. Jak mam to zrobić w twojej propozycji?
svick,
3
SO ma dyskusję? Gdzie jest mój BFG-3000 ...
Robert Harvey
2
@talkol Myślisz, że programowanie równoległe jest nieprzyzwoite? To, co najmniej interesujące, perspektywa, gdy mówisz async. Myślę, że to jedna z wielkich zalet async- await: pozwala łatwo komponować operacje asynchroniczne (i to nie tylko w najprostszy sposób „start A, czekanie na A, start B, czekanie na B”). I właśnie w tym celu istnieje specjalna składnia: nazywa się await.
svick,
1
@svick haha, teraz już tam poszliśmy :) Nie sądzę, aby równoległy prog był obsceniczny, ale myślę, że robienie tego z async-czekaniem jest. Async-czekaj to cukier syntaktyczny do utrzymywania synchronicznego stanu umysłu bez płacenia ceny blokowania. Jeśli już myślisz równolegle, zachęcam do użycia innego wzorca
talkol

Odpowiedzi:

9

Odpowiedź na twoje pytanie znajduje się już w łączącym się pytaniu SO.

Async / await ma na celu ułatwienie pisania kodu w świecie z wieloma operacjami o dużym opóźnieniu. Zdecydowana większość twoich operacji nie ma dużych opóźnień.

Kiedy pojawiła się WinRT , projektanci opisywali, w jaki sposób zdecydowali, które operacje będą asynchroniczne. Zdecydowali, że wszystko, co zajmie 50 ms lub więcej, będzie asynchroniczne, a pozostała część metod będzie zwykłymi, niesynchronicznymi metodami.

Ile metod trzeba było przepisać, aby były asynchroniczne? Około 10 procent z nich. Pozostałe 90% w ogóle nie uległo zmianie.

Eric Lippert wyjaśnia dość szczegółowo szczegóły techniczne, dlaczego zdecydowali się nie stosować jednego rozwiązania dla wszystkich. On w zasadzie mówi, że asynci awaitsą częściowe wdrożenie kontynuację przechodząc stylu, a że optymalizacja takiego stylu pasują do wszystkich przypadków jest trudny problem.

Robert Harvey
źródło
Zwróć uwagę na istotną różnicę między pytaniem SO a tym. SO pyta, dlaczego nie wszystko asynchronizować. Tutaj nie sugerujemy tego, sugerujemy wykonanie asynchronizacji 10%, po prostu przy użyciu tej samej składni, to wszystko. Korzystanie z bliższej składni ma tę zaletę, że można łatwiej zmienić, która 10% jest asynchroniczna, bez uszczerbku dla efektów domina z powodu tych zmian
talkol
Nie jestem pewien, dlaczego asyncmiałby wywoływać inwazję zombie. Nawet jeśli metoda wywołuje 10 innych metod, nie musisz po prostu przejść do asyncmetody najwyższego poziomu?
Robert Harvey
6
Powiedzmy, że 100% mojego obecnego kodu to synchronizacja. Teraz mam jedną wewnętrzną funkcję na poziomie liścia, która wysyła zapytanie do bazy danych, którą chciałbym zmienić na asynchroniczną. Teraz, aby było asynchroniczne, potrzebuję, aby jego osoba dzwoniąca była asynchroniczna, a osoba dzwoniąca asynchroniczna i tak dalej, aż do najwyższego poziomu. Oczywiście mówię o przypadku, w którym oczekuje się na cały łańcuch (aby zachować synchroniczny projekt kodu lub przekazać wartości zwrotne)
talkol