OK, ecco la mia opinione.
C'è qualcosa chiamato coroutine che è noto da decenni. ("Knuth and Hopper" -class "per decenni") Sono generalizzazioni di subroutine , in quanto non solo ottengono e rilasciano il controllo all'istruzione start e return, ma lo fanno anche a punti specifici ( punti di sospensione ). Una subroutine è una coroutine senza punti di sospensione.
Sono PLAIN FACILI da implementare con macro C, come mostrato nel seguente articolo su "protothreads". ( link ) Leggilo. Aspetterò ...
La linea di fondo di questo è che le macro creano una grande switch
e un'etichetta case
ad ogni punto di sospensione. In ogni punto di sospensione, la funzione memorizza il valore dell'etichetta case
immediatamente successiva, in modo che sappia dove riprendere l'esecuzione alla successiva chiamata. E restituisce il controllo al chiamante.
Questo viene fatto senza modificare il flusso apparente di controllo del codice descritto nel "protothread".
Immagina ora di avere un grosso ciclo che chiama a turno tutti questi "protothread", e si esegue simultaneamente "protothreads" su un singolo thread.
Questo approccio presenta due inconvenienti:
- Non puoi mantenere lo stato in variabili locali tra i resoconti.
- Non puoi sospendere il "protothread" da una profondità di chiamata arbitraria. (tutti i punti di sospensione devono essere al livello 0)
Esistono soluzioni alternative per entrambi:
- Tutte le variabili locali devono essere richiamate nel contesto del protothread (contesto che è già necessario dal fatto che il protothread deve memorizzare il successivo punto di ripresa)
- Se senti che hai davvero bisogno di chiamare un altro protothread da un protothread, "genera" un bambino protothread e sospendi fino al completamento del bambino.
E se avessi il supporto del compilatore per fare il lavoro di riscrittura che fanno le macro e la soluzione, beh, potresti semplicemente scrivere il tuo codice di protothread come vuoi e inserire punti di sospensione con una parola chiave.
E questo è quanto async
e await
riguardano: creazione di coroutine (senza stack).
Le coroutine in C # sono reificate come oggetti di classe (generica o non generica) Task
.
Trovo che queste parole chiave siano fuorvianti. La mia lettura mentale è:
-
async
come "sospensibile"
-
await
come "sospendi fino al completamento di"
-
Task
come "futuro ..."
Ora. Abbiamo davvero bisogno di contrassegnare la funzione async
? Oltre a dire che dovrebbe attivare i meccanismi di riscrittura del codice per rendere la funzione una coroutine, risolve alcune ambiguità. Considera questo codice.
public Task<object> AmIACoroutine() {
var tcs = new TaskCompletionSource<object>();
return tcs.Task;
}
Supponendo che async
non sia obbligatorio, si tratta di una coroutine o di una funzione normale? Il compilatore dovrebbe riscriverlo come una coroutine o no? Entrambi potrebbero essere possibili con diverse semantiche finali.