Perché abbiamo bisogno della parola chiave async?

15

Ho appena iniziato a giocare con async / await in .Net 4.5. Una cosa di cui inizialmente sono curioso, perché è necessaria la parola chiave async? La spiegazione che ho letto è che si tratta di un marker, quindi il compilatore sa che un metodo attende qualcosa. Ma sembra che il compilatore dovrebbe essere in grado di capirlo senza una parola chiave. Quindi che altro fa?

    
posta ConditionRacer 18.02.2013 - 22:25
fonte

4 risposte

19

Qui ci sono diverse risposte e tutte parlano dei metodi asincroni, ma nessuno risponde alla domanda, motivo per cui async è necessaria come parola chiave che va nella dichiarazione della funzione.

Non è "dire al compilatore di trasformare la funzione in un modo speciale"; await da solo potrebbe farlo. Perché? Poiché C # ha già un altro meccanismo in cui la presenza di una parola chiave speciale nel corpo del metodo fa sì che il compilatore esegua trasformazioni estreme (e molto simili a async/await ) sul corpo del metodo: yield .

Tranne che yield non è la sua parola chiave in C # e capire perché spiegherà anche async . A differenza della maggior parte delle lingue che supportano questo meccanismo, in C # non si può dire yield value; . Si deve invece dire yield return value; . Perché? Perché è stato aggiunto alla lingua dopo che C # esisteva già ed era del tutto ragionevole supporre che qualcuno, da qualche parte, avrebbe potuto usare yield come nome di una variabile. Ma poiché non esisteva uno scenario preesistente in cui <variable name> return era sintatticamente corretto, yield return è stato aggiunto al linguaggio per rendere possibile l'introduzione di generatori mantenendo al contempo la compatibilità al 100% con il codice esistente.

Ed è per questo che async è stato aggiunto come modificatore di funzione: per evitare di rompere il codice esistente che ha utilizzato await come nome di variabile. Poiché nessun metodo async esiste già, nessun vecchio codice è invalidato e nel nuovo codice il compilatore può utilizzare la presenza del tag async per sapere che await deve essere trattato come una parola chiave e non come un identificatore.

    
risposta data 05.11.2015 - 17:14
fonte
6

cambia il metodo da un metodo normale a un oggetto con callback che richiede un approccio totalmente diverso per la generazione del codice

e quando succede qualcosa di drastico come questo, è consuetudine indicarlo chiaramente (abbiamo imparato quella lezione dal C ++)

    
risposta data 18.02.2013 - 22:33
fonte
4

L'intera idea con parole chiave come "async" o "non sicuro" serve a rimuovere l'ambiguità su come il codice che modificano debba essere trattato. Nel caso della parola chiave async, dice al compilatore di considerare il metodo modificato come qualcosa che non ha bisogno di ritornare immediatamente. Ciò consente al thread in cui viene utilizzato questo metodo per continuare senza dover attendere i risultati di tale metodo. È efficacemente l'ottimizzazione del codice.

    
risposta data 18.02.2013 - 22:34
fonte
0

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:

  1. Non puoi mantenere lo stato in variabili locali tra i resoconti.
  2. 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:

  1. 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)
  2. 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.

    
risposta data 05.11.2015 - 11:15
fonte

Leggi altre domande sui tag