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 switchi caseetykiety w każdym punkcie zawieszenia. W każdym punkcie zawieszenia funkcja przechowuje wartość caseetykiety 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:
- Nie można zachować stanu w zmiennych lokalnych między wznowieniami.
- Nie można zawiesić „protothread” z dowolnej głębokości połączenia. (wszystkie punkty zawieszenia muszą znajdować się na poziomie 0)
Istnieją obejścia dla obu:
- Wszystkie zmienne lokalne muszą zostać wyciągnięte do kontekstu protothread (kontekst, który jest już potrzebny, ponieważ protothread musi przechowywać swój następny punkt wznowienia)
- Jeśli czujesz, że naprawdę musisz zadzwonić z innego protothreada z „protothread”, „spawn” dziecięcy protothread i zawieś go do czasu ukończenia go.
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 asynci awaitto 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.
public Task<object> AmIACoroutine() {
var tcs = new TaskCompletionSource<object>();
return tcs.Task;
}
Zakładając, że asyncnie 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ą.