Właśnie zacząłem bawić się w async / czekaj w .Net 4.5. Na początku jestem ciekawy, dlaczego słowo kluczowe asynchroniczne jest potrzebne? Wyjaśnienie, które przeczytałem było takie, że jest to marker, więc kompilator wie, że metoda na coś czeka. Ale wydaje się, że kompilator powinien być w stanie to zrozumieć bez słowa kluczowego. Co jeszcze to robi?
19
Odpowiedzi:
Jest tu kilka odpowiedzi i wszystkie mówią o tym, co robią metody asynchroniczne, ale żadna z nich nie odpowiada na pytanie, dlatego
async
jest potrzebne jako słowo kluczowe, które znajduje się w deklaracji funkcji.To nie jest „polecenie kompilatorowi, aby przekształcił funkcję w specjalny sposób”;
await
sam mógł to zrobić. Dlaczego? Ponieważ C # ma już inny mechanizm, gdzie obecność specjalnego słowa kluczowego w treści metody powoduje kompilator wykonać skrajny (i bardzo podobna doasync/await
) transformacje na korpusie metodą:yield
.Tyle że
yield
nie jest to własne słowo kluczowe w języku C # i zrozumienie, dlaczego to wyjaśniasync
. W przeciwieństwie do większości języków obsługujących ten mechanizm, w języku C # nie można powiedzieć,yield value;
że trzeba powiedziećyield return value;
. Dlaczego? Ponieważ został dodany do języka po tym, jak C # już istniał, i całkiem rozsądne było założenie, że ktoś gdzieś mógł użyćyield
jako nazwy zmiennej. Ponieważ jednak nie istniał wcześniejszy scenariusz, w którym<variable name> return
byłby poprawny składni,yield return
dodano do języka, aby umożliwić wprowadzenie generatorów przy jednoczesnym zachowaniu 100% wstecznej zgodności z istniejącym kodem.I dlatego
async
został dodany jako modyfikator funkcji: aby uniknąć zepsucia istniejącego kodu, który był używanyawait
jako nazwa zmiennej. Ponieważ nieasync
istniały już żadne metody, stary kod nie jest unieważniany, aw nowym kodzie kompilator może wykorzystać obecnośćasync
znacznika, aby wiedzieć, żeawait
należy go traktować jako słowo kluczowe, a nie identyfikator.źródło
async
można zrezygnować z wymogu podania słowa kluczowego? Oczywiście byłby to przełom BC, ale można by pomyśleć, że wpłynie na bardzo niewiele projektów i będzie prostym wymogiem aktualizacji (tj. Zmiana nazwy zmiennej).zmienia metodę z normalnej na obiekt z wywołaniem zwrotnym, co wymaga zupełnie innego podejścia do generowania kodu
a kiedy dzieje się coś tak drastycznego, zwyczajowo wyraźnie to zaznacza się (nauczyliśmy się tej lekcji od C ++)
źródło
async
s jednocześnie, aby nie działały jeden po drugim, ale jednocześnie (jeśli wątki są przynajmniej dostępne)Task<T>
faktycznie ma charakterystykęasync
metody - na przykład możesz po prostu przejrzeć logikę biznesową / fabryczną, a następnie przekazać ją innej powracającej metodzieTask<T>
.async
metody mają typ zwracanyTask<T>
w podpisie, ale tak naprawdę nie zwracają aTask<T>
, po prostu zwracają aT
. Aby to wszystko zrozumieć bezasync
słowa kluczowego, kompilator C # musiałby przeprowadzić różnego rodzaju głęboką inspekcję metody, co prawdopodobnie spowolniłoby ją nieco i doprowadziłoby do wszelkich niejasności.Cały pomysł ze słowami kluczowymi takimi jak „asynchroniczny” lub „niebezpieczny” polega na usunięciu niejednoznaczności co do sposobu traktowania modyfikowanego przez nich kodu. W przypadku słowa kluczowego async informuje kompilator, aby traktował zmodyfikowaną metodę jako coś, co nie musi natychmiast wracać. Pozwala to wątkowi, w którym ta metoda jest używana, kontynuować bez konieczności oczekiwania na wyniki tej metody. To skutecznie optymalizacja kodu.
źródło
OK, oto moje zdanie na ten temat.
Istnieje coś zwanego coroutines, które jest znane od dziesięcioleci. („Knuth and Hopper” -class „od dziesięcioleci”) Są uogólnieniami podprogramów , w tym, że nie tylko uzyskują i zwalniają kontrolę przy instrukcji początkowej i zwrotnej funkcji, ale także robią to w określonych punktach ( punktach zawieszenia ). Podprogram jest korupcją bez punktów zawieszenia.
Są ŁATWE w implementacji z makrami C, jak pokazano w poniższym artykule na temat „protothreads”. ( http://dunkels.com/adam/dunkels06protothreads.pdf ) Przeczytaj. Poczekam...
Najważniejsze jest to, że makra tworzą duże
switch
icase
etykiety w każdym punkcie zawieszenia. W każdym punkcie zawieszenia funkcja przechowuje wartośćcase
etykiety bezpośrednio po niej , dzięki czemu wie, gdzie wznowić wykonywanie przy następnym wywołaniu. I zwraca kontrolę dzwoniącemu.Odbywa się to bez modyfikowania pozornego przepływu kontroli kodu opisanego w „protothread”.
Wyobraź sobie teraz, że masz dużą pętlę, która z kolei nazywa wszystkie te „protothreads” i jednocześnie możesz wykonywać „protothreads” w jednym wątku.
To podejście ma dwie wady:
Istnieją obejścia dla obu:
A jeśli posiadasz wsparcie kompilatora do wykonywania operacji przepisywania, które wykonują makra i obejście, cóż, możesz po prostu napisać swój protothread code tak, jak chcesz i wstawić punkty zawieszenia słowem kluczowym.
I to
async
iawait
to chodzi: tworzenie (Stackless) współprogram.Korpusy w C # są reifikowane jako obiekty klasy (ogólnej lub nie-ogólnej)
Task
.Uważam te słowa kluczowe za bardzo mylące. Moje mentalne czytanie to:
async
jako „zawieszalny”await
jako „zawieś do zakończenia”Task
jako „przyszłość…”Teraz. Czy naprawdę musimy zaznaczyć tę funkcję
async
? Oprócz powiedzenia, że powinien uruchomić mechanizmy przepisywania kodu, aby funkcja stała się coroutine, rozwiązuje pewne niejasności. Rozważ ten kod.Zakładając, że
async
nie jest to obowiązkowe, czy jest to korupcja czy normalna funkcja? Czy kompilator powinien przepisać go jako coroutine, czy nie? Oba mogłyby być możliwe z inną ewentualną semantyką.źródło