C # 5 Async / Attesa – è * concurrent *?

Ho preso in considerazione la nuova roba asincrona in C # 5, e una domanda particolare è venuta fuori.

Comprendo che la parola chiave await è un accurato trucco del compilatore / zucchero sintattico per implementare il passaggio di continuazione , dove il resto del metodo viene suddiviso in oggetti Task e in coda per essere eseguito nell’ordine, ma dove il controllo viene restituito al metodo di chiamata .

Il mio problema è che ho sentito che al momento questo è tutto su un singolo thread. Questo significa che questa roba asincrona è in realtà solo un modo per trasformare il codice di continuazione in oggetti Task e quindi chiamare Application.DoEvents() dopo che ciascuna attività è stata completata prima di avviare quella successiva?

O mi sta sfuggendo qualcosa? (Questa parte della domanda è retorica – Sono pienamente consapevole che mi manca qualcosa :))

Molte grazie in anticipo.

    È concomitante , nel senso che molte operazioni asincrone in sospeso possono essere in corso in qualsiasi momento. Può essere o non essere multithreaded .

    Per impostazione predefinita, await pianificherà la continuazione al “contesto di esecuzione corrente”. Il “contesto di esecuzione corrente” è definito come SynchronizationContext.Current se non è null , o TaskScheduler.Current se non esiste SynchronizationContext .

    È ansible ignorare questo comportamento predefinito chiamando ConfigureAwait e passando false per il parametro continueOnCapturedContext . In tal caso, la continuazione non verrà pianificata di nuovo in quel contesto di esecuzione. Questo di solito significa che verrà eseguito su un thread del threadpool.

    A meno che non si stia scrivendo il codice della libreria, il comportamento predefinito è esattamente ciò che si desidera. WinForms, WPF e Silverlight (ovvero tutti i framework dell’interfaccia utente) forniscono un SynchronizationContext , quindi la continuazione viene eseguita sul thread dell’interfaccia utente (e può accedere in modo sicuro agli oggetti dell’interfaccia utente). ASP.NET fornisce anche un SynchronizationContext che assicura che la continuazione venga eseguita nel contesto della richiesta corretta.

    Altri thread (inclusi thread del threadpool, Thread e BackgroundWorker ) non forniscono un SynchronizationContext . Pertanto, le app Console e i servizi Win32 per impostazione predefinita non dispongono di un SynchronizationContext . In questa situazione, le continue vengono eseguite sui thread del threadpool. Questo è il motivo per cui le demo delle app della console che utilizzano Wait / async includono una chiamata a Console.ReadLine / ReadKey o eseguono un blocco Wait su un’attività.

    Se ti senti necessario un SynchronizationContext , puoi usare AsyncContext dalla mia libreria Nito.AsyncEx ; in pratica fornisce semplicemente un “ciclo principale” async con un SynchronizationContext . Trovo utile per le app di console e test di unità (VS2012 ora ha il supporto integrato per i test di unità async Task ).

    Per ulteriori informazioni su SynchronizationContext , vedere l’articolo di MSDN di febbraio .

    In nessun momento DoEvents o un equivalente chiamato; piuttosto, il stream di controllo ritorna completamente e la continuazione (il resto della funzione) è programmata per essere eseguita più tardi. Questa è una soluzione molto più pulita perché non causa problemi di rientranza come avresti se fosse stato utilizzato DoEvents .

    L’idea che sta dietro async / await è che esegue la continuazione passando piacevolmente e non assegna un nuovo thread per l’operazione. La continuazione potrebbe verificarsi su un nuovo thread, potrebbe continuare sullo stesso thread.

    La vera parte “carne” (asincrona) di async / await viene normalmente eseguita separatamente e la comunicazione al chiamante avviene tramite TaskCompletionSource. Come scritto qui http://blogs.msdn.com/b/pfxteam/archive/2009/06/02/9685804.aspx

    Il tipo TaskCompletionSource ha due scopi correlati, entrambi a cui fa riferimento il nome: è una fonte per la creazione di un’attività e l’origine per il completamento di tale attività. In sostanza, una TaskCompletionSource funge da produttore per un’attività e il suo completamento.

    e l’esempio è abbastanza chiaro:

     public static Task RunAsync(Func function) { if (function == null) throw new ArgumentNullException(“function”); var tcs = new TaskCompletionSource(); ThreadPool.QueueUserWorkItem(_ => { try { T result = function(); tcs.SetResult(result); } catch(Exception exc) { tcs.SetException(exc); } }); return tcs.Task; } 

    Tramite TaskCompletionSource è ansible accedere a un object Task che è ansible attendere, ma non tramite le parole chiave asincrone / attese che è stato creato il multithreading.

    Notare che quando molte funzioni “lente” verranno convertite nella syntax asincrona / attesa, non sarà necessario utilizzare TaskCompletionSource . Lo useranno internamente (ma alla fine da qualche parte ci deve essere un TaskCompletionSource per avere un risultato asincrono)

    Il modo in cui mi piace spiegarlo è che la parola chiave “await” attende semplicemente che un’operazione finisca, ma restituisce l’esecuzione al thread chiamante durante l’attesa. Quindi restituisce il risultato dell’attività e continua dall’istruzione dopo la parola chiave “Attendere” una volta completata l’attività.

    Alcune persone che ho notato sembrano pensare che l’attività sia eseguita nello stesso thread del thread chiamante, questo non è corretto e può essere provato provando ad alterare un elemento della GUI di Windows.Forms all’interno del metodo che attende le chiamate. Tuttavia, la continuazione viene eseguita nel thread chiamante dove mai ansible.

    È un modo semplice per non dover avere delegati di callback o gestori di eventi per quando l’attività termina.