Share via


Il presente articolo è stato tradotto automaticamente.

Async/attendono

Best Practices in programmazione asincrona

Stephen Cleary

 

In questi giorni c'è una ricchezza di informazioni circa il nuovo async e aspettano il supporto in ambito Microsoft .NET 4.5. Questo articolo è inteso come una "seconda fase" in apprendimento programmazione asincrona; Presumo che hai letto almeno un articolo introduttivo su di esso. Questo articolo presenta nulla di nuovo, come il Consiglio stesso può essere trovato on-linea nelle fonti come Stack Overflow, forum MSDN e FAQ async/attendono. Questo articolo evidenzia solo alcune best practice che possono perdersi nella valanga di documentazione disponibile.

Le migliori pratiche in questo articolo sono più quello che si chiamerebbe "linee guida" rispetto le regole reali. Ci sono eccezioni a ciascuna di queste linee guida. Ti spiego il ragionamento che sta dietro ogni orientamento che risulta evidente quando si fa e non si applica. Le linee guida sono riassunte Figura 1; Tratterò ciascuno nelle sezioni seguenti.

Figura 1 Sommario delle linee guida di programmazione asincrona

Name Descrizione Eccezioni
Evitare async vuoto Preferire metodi attività asincrone async metodi void Gestori eventi
Async tutto il modo Don' t mix di blocco e codice asincrono Metodo principale console
Configurare il contesto È possibile utilizzare ConfigureAwait(false) Metodi che richiedono con­testo

Evitare Async vuoto

Ci sono tre possibili tipi restituiti di metodi asincroni: Attività, attività <T> e vuoto, ma il naturale ritorno tipi per i metodi asincroni sono solo attività e attività <T>. Conversione da sincrono in codice asincrono, qualsiasi metodo che restituisce un tipo T diventa un metodo async Task <T> di ritorno, e qualsiasi metodo che restituisce void diventa un metodo asincrono restituendo Task. Il frammento di codice seguente viene illustrato un metodo sincrono di ritorno a vuoto e il suo equivalente asincrono:

void MyMethod()
{
  // Do synchronous work.
Thread.Sleep(1000);
}
async Task MyMethodAsync()
{
  // Do asynchronous work.
await Task.Delay(1000);
}

Metodi che restituiscono void async hanno uno scopo specifico: per rendere possibile i gestori eventi asincroni. È possibile disporre di un gestore di eventi che restituisce alcuni tipo effettivo, ma che non funziona bene con la lingua; richiamando un gestore eventi che restituisce che un tipo è molto scomodo e la nozione di un gestore di eventi effettivamente ritorno che qualcosa non ha molto senso. I gestori eventi naturalmente restituiscono void, quindi metodi asincroni restituiscono void in modo che si può avere un gestore di eventi asincroni. Tuttavia, alcuni semantica di un metodo void async è leggermente differente rispetto alla semantica di un'attività asincrona o async Task <T> Metodo.

Metodi void Async hanno una semantica diversa gestione degli errori. Quando viene generata un'eccezione fuori di un'attività asincrona o async Task <T> Metodo, che eccezione è catturato e messo sull'oggetto dell'attività. Con metodi void async, non ci è nessun oggetto dell'attività, tutte le eccezioni generate da un metodo void async verranno generato direttamente sull'oggetto SynchronizationContext che era attivo quando il metodo void async iniziato. Figura 2 illustra che le eccezioni generate da metodi void asincrone non possono essere catturate naturalmente.

Figura 2 eccezioni da un metodo Void Async non possono essere catturate con fermo

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
throw;
  }
}

Queste eccezioni possono essere osservate con UnhandledException o un evento simile catch-all per GUI /ASP.NET applicazioni, ma utilizzando tali eventi per la gestione delle eccezioni regolare è una ricetta per unmaintainability.

Metodi void Async hanno una semantica diversa composizione. Metodi asincroni ritorno attività o attività <T> può essere facilmente composto utilizzando attendono, in Task.WhenAny, Task.WhenAll e così via. Metodi asincroni tornando Sub non forniscono un modo semplice per notificare il codice chiamante che hai completato. È facile iniziare diversi metodi void async, ma non è facile determinare quando hanno finito. Metodi void Async notificherà loro SynchronizationContext quando inizio e di fine, ma un SynchronizationContext personalizzato è una soluzione complessa per il codice di applicazione regolare.

Metodi void Async sono difficili da testare. A causa delle differenze nell'errore di manipolazione e comporre, è difficile scrivere unit test che chiamare async metodi void. Il supporto di test asincrono MSTest funziona solo per i metodi asincroni ritorno attività o attività <T>. È possibile installare un SynchronizationContext che rileva quando hanno completato tutti i metodi void async e raccoglie tutte le eccezioni, ma è molto più facile fare solo i metodi void async Task viene restituito invece.

È chiaro che metodi void async hanno diversi svantaggi rispetto ai metodi di attività asincrone, ma sono abbastanza utili in un caso particolare: gestori di eventi asincroni. Le differenze semantiche ha senso per i gestori di eventi asincroni. Sollevano le eccezioni direttamente su SynchronizationContext, che è simile ai gestori eventi sincroni come comportarsi. I gestori eventi sincroni sono di solito privati, quindi non può essere composta o direttamente testati. È un approccio mi piace prendere per ridurre al minimo il codice nel mio gestore eventi asincroni — hanno, ad esempio, aspettano un async Metodo Task che contiene la logica effettiva. Il codice seguente illustra questo approccio, utilizzando metodi void async per gestori eventi senza sacrificare la testabilità:

private async void button1_Click(object sender, EventArgs e)
{
  await Button1ClickAsync();
}
public async Task Button1ClickAsync()
{
  // Do asynchronous work.
await Task.Delay(1000);
}

Metodi void Async possono devastare se il chiamante non è aspetta per essere asincrona. Quando il tipo restituito è compito, il chiamante sa si occupa di una futura operazione; Quando il tipo restituito è void, il chiamante potrebbe assumere che il metodo è completo per il momento che restituisce. Questo problema può sorgere in molti modi inaspettati. È solito sbagliato fornire un'implementazione asincrona (o ignorare) di un metodo di restituzione vuoto su un'interfaccia (o classe base). Alcuni eventi anche supporre che i gestori sono completi quando ritornano. Una sottile trappola è passando una lambda asincrona a un metodo di assunzione di un parametro di azione; in questo caso, la lambda async restituisce void ed eredita tutti i problemi dei metodi void async. Come regola generale, async lambda devono essere utilizzate solo se siete convertiti in un tipo delegato che restituisce il compito (per esempio, Func <Task>).

Per riassumere questo primo orientamento, si dovrebbe preferisce async Task async vuoto. Metodi Async Task attivare più facile gestione degli errori, componibilità e testabilità. L'eccezione a questa linea guida è gestori eventi asincroni, che devono restituire void. Questa eccezione include metodi che sono logicamente i gestori eventi, anche se non sono letteralmente i gestori di eventi (ad esempio, le implementazioni ICommand).

Async tutto il modo

Codice asincrono mi ricorda la storia di un collega che ha detto che il mondo è stato sospeso nello spazio e fu immediatamente contestato da un'anziana signora, sostenendo che il mondo si riposava sul dorso di una tartaruga gigantesca. Quando l'uomo ha chiesto che cosa la tartaruga era in piedi, la donna rispose: "Tu sei molto intelligente, giovane uomo, ma si tratta di tartarughe tutto il senso giù!" Come si converte codice sincrono in codice asincrono, troverete che funziona meglio se asincrono codice chiamate e viene chiamato da altro codice asincrono — tutto il senso giù (o "up", se si preferisce). Anche altri hanno notato il comportamento diffusione di asincrono di programmazione e hanno chiamato "contagiosa" o paragonato a un virus zombie. Se le tartarughe o zombie, è sicuramente vero che codice asincrono tende a guidare codice circostante per essere anche asincrona. Questo comportamento è inerente a tutti i tipi di programmazione asincrona, non appena le nuove async/attendono parole.

"Async all the way" significa che si non dovrebbe mescolare codice sincrono e asincrono senza considerare attentamente le conseguenze. In particolare, è di solito una cattiva idea di bloccare il codice asincrono chiamando wait o Task.Result. Questo è un problema comune soprattutto per i programmatori che sono "immergendo le dita dei piedi" in programmazione asincrona, solo una piccola parte della loro applicazione di conversione e avvolgendolo in un API sincrono quindi il resto dell'applicazione è isolato dai cambiamenti. Purtroppo, si imbattono in problemi con deadlock. Dopo aver risposto a molte domande relative async su MSDN forum, Stack Overflow e posta elettronica, posso dire che questo è di gran lunga la domanda ha chiesto la maggior parte di nuovi arrivati async, una volta che imparano i principi fondamentali: "Perché il mio async parzialmente codice deadlock?"

Figura 3 viene illustrato un semplice esempio dove un metodo blocca sul risultato di un metodo asincrono. Questo codice funziona bene in un'applicazione console, ma sarà deadlock quando viene chiamato da un contesto GUI o ASP.NET . Questo comportamento può essere fonte di confusione, soprattutto se si considera che il passo attraverso il debugger implica che è l'attesa che non viene mai completata. La vera causa di deadlock è più in alto nello stack di chiamate quando viene chiamato Wait.

Figura 3 comuni Deadlock problema quando blocco su Async codice

public static class DeadlockDemo
{
  private static async Task DelayAsync()
  {
    await Task.Delay(1000);
  }
  // This method causes a deadlock when called in a GUI or ASP.NET context.
public static void Test()
  {
    // Start the delay.
var delayTask = DelayAsync();
    // Wait for the delay to complete.
delayTask.Wait();
  }
}

La causa principale di questa situazione di stallo è dovuta al modo attendono maniglie contesti. Per impostazione predefinita, quando un compito incompleto è atteso, il "contesto" corrente viene catturato e utilizzato per il metodo resume al completamento dell'attività. Questo "contesto" è l'oggetto SynchronizationContext corrente a meno che sia null, nel qual caso è il TaskScheduler corrente. Applicazioni GUI e ASP.NET hanno un SynchronizationContext che consente solo un pezzo di codice da eseguire in un momento. Al termine dell'attesa, tenta di eseguire il resto del metodo asincrono all'interno del contesto catturato. Ma quel contesto ha già un thread in esso, che (in modo sincrono) è attesa per il metodo asincrono completare. They're ogni attesa per l'altro, causando una situazione di stallo.

Si noti che le applicazioni console non causano questa situazione di stallo. Essi hanno un pool di thread SynchronizationContext anziché un SynchronizationContext un pezzo alla volta, così quando viene completata l'attesa, pianifica il resto del metodo asincrono su un thread del pool. Il metodo è in grado di completare, che completa la sua attività restituita, e non non c'è nessuna situazione di stallo. Questa differenza di comportamento può essere confuso quando i programmatori scrivono un programma di test console, osservare l'async parzialmente codice lavoro, come previsto, e poi sposta lo stesso codice in un'applicazione GUI o ASP.NET , dove esso deadlocks.

La migliore soluzione a questo problema è per consentire al codice asincrono a crescere naturalmente attraverso il codebase. Se si segue questa soluzione, vedrai async codice espandere al relativo punto di ingresso, di solito un evento gestore o controller di azione. Applicazioni console non possono seguire questa soluzione completamente perché il metodo principale non può essere asincrona. Se il metodo Main async, esso potrebbe tornare prima di esso completato, causando il programma alla fine. Figura 4 dimostra l'orientamento di questa eccezione: Il metodo principale per un'applicazione console è una delle poche situazioni dove il codice può bloccare su un metodo asincrono.

Figura 4 il metodo principale può chiamare wait o Task.Result

class Program
{
  static void Main()
  {
    MainAsync().Wait();
  }
  static async Task MainAsync()
  {
    try
    {
      // Asynchronous implementation.
await Task.Delay(1000);
    }
    catch (Exception ex)
    {
      // Handle exceptions.
}
  }
}

Permettendo async a crescere attraverso il codebase è la soluzione migliore, ma questo significa che c'è un sacco di lavoro iniziale di un'applicazione per vedere il reale beneficio dal codice asincrono. Ci sono alcune tecniche per la conversione in modo incrementale di un grande codebase di async codice, ma che stanno di fuori della portata di questo articolo. In alcuni casi, utilizzando wait o Task.Result può aiutare con una conversione parziale, ma è necessario essere consapevoli del problema del deadlock come pure il problema di gestione degli errori. Farò spiegare ora il problema di gestione degli errori e mostrare come evitare il problema di stallo più avanti in questo articolo.

Ogni compito archivierà un elenco di eccezioni. Quando si attesa un'attività, la prima eccezione è generata, quindi si può prendere il tipo di eccezione specifica (ad esempio InvalidOperationException). Tuttavia, quando si blocca in modo sincrono su un Task utilizzando wait o Task.Result, tutte le eccezioni sono avvolti in un AggregateException e generate. Si riferiscono ancora a Figura 4. Try/catch in MainAsync cattura un tipo di eccezione specifica, ma se metti il try/catch in Main, quindi sarà sempre recuperare un AggregateException. Gestione degli errori è molto più facile da affrontare quando non hai un AggregateException, così ho messo il "globale" try/catch in MainAsync.

Finora, ho mostrato due problemi con blocco sul codice asincrono: possibili deadlock e gestione degli errori più complicata. C'è anche un problema con l'utilizzo di codice di blocco all'interno di un metodo asincrono. Si consideri questo semplice esempio:

public static class NotFullyAsynchronousDemo
{
  // This method synchronously blocks a thread.
public static async Task TestNotFullyAsync()
  {
    await Task.Yield();
    Thread.Sleep(5000);
  }
}

Questo metodo non è completamente asincrono. Esso sarà immediatamente resa, restituendo un compito incompleto, ma quando riprende in modo sincrono si bloccherà qualunque thread è in esecuzione. Se questo metodo viene chiamato da un contesto di GUI, bloccherà il thread della GUI; Se viene chiamato da un contesto della richiesta ASP.NET , bloccherà il thread di richiesta ASP.NET corrente. Codice asincrono funziona meglio se non bloccare in modo sincrono. Figura 5 è un cheat sheet di async sostituzioni per operazioni sincrone.

Figura 5 il modo"Async" di fare le cose

Per fare questo... Invece di questo... Utilizzare questo
Recuperare il risultato di un'attività in background Wait o Task.Result vi aspettano
Attendere per qualsiasi attività completare WaitAny vi aspettano Task.WhenAny
Recuperare i risultati di più attività WaitAll vi aspettano Task.WhenAll
Attendere un periodo di tempo Thread. Sleep vi aspettano Task.Delay

Per riassumere questo secondo orientamento, si dovrebbe evitare di miscelazione async e codice di blocco. Async mista e il codice di blocco può causare deadlock, gestione degli errori più complessi e inaspettato blocco del thread di contesto. L'eccezione a questa linea guida è il metodo principale per le applicazioni di console, o — se sei un utente avanzato — gestione di una base di codice parzialmente asincrono.

Configurare il contesto

In questo articolo, ho spiegato brevemente come "contesto" è catturato per impostazione predefinita, quando è atteso un compito incompleto, e che questo contesto catturato viene utilizzato per riprendere il metodo asincrono. L'esempio in Figura 3 illustrato come ripresa sugli scontri di contesto con blocco sincrono per causare un deadlock. Questo comportamento di contesto può anche causare un altro problema — una delle prestazioni. Come crescono le applicazioni GUI asincrone, potreste trovare molte piccole parti di metodi asincroni tutti utilizzando il thread della GUI come loro contesto. Questo può causare lentezza come reattività soffre di "migliaia di tagli di carta".

Per limitare questo problema, attendono il risultato di ConfigureAwait ogni volta che puoi. Il frammento di codice seguente viene illustrato il comportamento del contesto predefinito e l'uso di ConfigureAwait:

async Task MyMethodAsync()
{
  // Code here runs in the original context.
await Task.Delay(1000);
  // Code here runs in the original context.
await Task.Delay(1000).ConfigureAwait(
    continueOnCapturedContext: false);
  // Code here runs without the original
  // context (in this case, on the thread pool).
}

Utilizzando ConfigureAwait, si attiva una piccola quantità di parallelismo: Qualche codice asincrono può essere eseguito in parallelo con il thread della GUI invece costantemente lo badgering con bit di lavoro da fare.

A parte le prestazioni, ConfigureAwait ha un altro aspetto importante: Può evitare il deadlock. Considerare Figura 3 ancora; Se si aggiunge "ConfigureAwait(false)" per la riga di codice in DelayAsync, la situazione di stallo è evitato. Questa volta, quando viene completata l'attesa, tenta di eseguire il resto del metodo asincrono all'interno del contesto del pool di thread. Il metodo è in grado di completare, che completa la sua attività restituita, e non non c'è nessuna situazione di stallo. Questa tecnica è particolarmente utile se avete bisogno di convertire gradualmente un'applicazione da sincrono asincroni.

Se è possibile utilizzare ConfigureAwait a un certo punto all'interno di un metodo, quindi vi consiglio di usare per ogni attendono in quel metodo dopo quel punto. Ricordare che il contesto è catturato solo se è atteso un compito incompleto; Se l'attività è già completo, il contesto non è catturato. Alcune attività potrebbero completare più velocemente del previsto in situazioni di rete e hardware diverso, e devi gestire gentilmente un'attività restituita che completa prima di esso è atteso. Figura 6 Mostra un esempio modificato.

Figura 6 gestisce un'attività restituito che completa la prima è atteso

async Task MyMethodAsync()
{
  // Code here runs in the original context.
await Task.FromResult(1);
  // Code here runs in the original context.
await Task.FromResult(1).ConfigureAwait(continueOnCapturedContext: false);
  // Code here runs in the original context.
var random = new Random();
  int delay = random.Next(2); // Delay is either 0 or 1
  await Task.Delay(delay).ConfigureAwait(continueOnCapturedContext: false);
  // Code here might or might not run in the original context.
// The same is true when you await any Task
  // that might complete very quickly.
}

Non si deve usare ConfigureAwait, quando si dispone di codice dopo l'attesa nel metodo che ha bisogno del contesto. Per le applicazioni GUI, questo include qualsiasi codice che manipola elementi GUI, scrive le proprietà con associazione a dati o dipende da un tipo specifico GUI come Dispatcher/CoreDispatcher. Per le applicazioni ASP.NET , questo include qualsiasi codice che utilizza HttpContext o costruisce una risposta ASP.NET , comprese istruzioni return in azioni controller. Figura 7viene illustrato uno schema comune in applicazioni GUI — avendo un gestore di eventi asincroni disabilitare il controllo all'inizio del metodo, eseguire alcune aspetta e quindi riattivare il controllo alla fine del gestore; il gestore eventi non può rinunciare al suo contesto perché ha bisogno di riattivare il suo controllo.

Figura 7 avendo un evento Async gestore disattivare e riattivare il controllo

private async void button1_Click(object sender, EventArgs e)
{
  button1.Enabled = false;
  try
  {
    // Can't use ConfigureAwait here ...
await Task.Delay(1000);
  }
  finally
  {
    // Because we need the context here.
button1.Enabled = true;
  }
}

Ogni metodo asincrono ha un proprio contesto, quindi se un metodo asincrono chiama un altro metodo async, loro contesti sono indipendenti. Figura 8 Mostra una piccola modifica del Figura 7.

Figura 8 ogni metodo Async ha il proprio contesto

private async Task HandleClickAsync()
{
  // Can use ConfigureAwait here.
await Task.Delay(1000).ConfigureAwait(continueOnCapturedContext: false);
}
private async void button1_Click(object sender, EventArgs e)
{
  button1.Enabled = false;
  try
  {
    // Can't use ConfigureAwait here.
await HandleClickAsync();
  }
  finally
  {
    // We are back on the original context for this method.
button1.Enabled = true;
  }
}

Codice context-free è più riutilizzabile. Cercare di creare una barriera in codice tra il codice sensibile al contesto e codice context-free e ridurre al minimo il codice sensibile al contesto. In Figura 8, vi consiglio di mettere tutta la logica di nucleo del gestore eventi all'interno di un async testabile e context-free Metodo Task, lasciando solo il minimo codice nel gestore eventi sensibile al contesto. Anche se si sta scrivendo un'applicazione ASP.NET , se si dispone di una libreria di base che potenzialmente è condivisa con le applicazioni desktop, considerare l'utilizzo di ConfigureAwait nel codice della libreria.

Per riassumere questa terza linea guida, è necessario utilizzare Configura­attendono quando possibile. Codice context-free ha prestazioni migliori per le applicazioni GUI ed è una tecnica utile per evitare deadlock quando si lavora con un codebase parzialmente async. Le eccezioni a questa linea guida sono metodi che richiedono il contesto.

Conoscere gli strumenti

C'è molto da imparare circa async e aspettano, ed è naturale per ottenere un po ' disorientato. Figura 9 è un riferimento rapido delle soluzioni ai problemi più comuni.

Figura 9 soluzioni ai problemi più comuni Async

Problema Soluzione:
Creare un'attività per eseguire codice Task.Run o TaskFactory (non il Costruttore Task o Task)
Creare un wrapper di attività per un'operazione o un evento. TaskFactory o TaskCompletionSource <T>
Supportano l'annullamento CancellationTokenSource e CancellationToken
Rapporto di avanzamento IProgress <T> e il progresso <T>
Gestire flussi di dati TPL Dataflow o Reactive Extensions
Sincronizzare l'accesso a una risorsa condivisa SemaphoreSlim
Inizializzare in modo asincrono una risorsa AsyncLazy <T>
Strutture Async-pronto producer-consumer TPL Dataflow o AsyncCollection <T>

Il primo problema è la creazione di attività. Ovviamente, un metodo asincrono può creare un'attività, e che è l'opzione più semplice. Se avete bisogno di eseguire codice nel pool di thread, utilizzare Task.Run. Se si desidera creare un wrapper di attività per un'operazione asincrona esistente o un evento, utilizzare TaskCompletionSource <T>. Il prossimo problema comune è come gestire la cancellazione e la segnalazione di progresso. The base class library (BCL) include tipi specificamente destinati a risolvere questi problemi: CancellationTokenSource/CancellationToken e IProgress <T> / <T> il progresso. Codice asincrono deve utilizzare il modello asincrono basato su attività, o toccare (msdn.microsoft.com/library/hh873175), che spiega la creazione di attività, annullamento e nel dettaglio le relazioni sull'avanzamento.

Un altro problema che si presenta è come gestire flussi di dati asincroni. I compiti sono grandi, ma possono restituire solo un oggetto e completare solo una volta. Per flussi asincroni, è possibile utilizzare TPL Dataflow o Reactive Extensions (Rx). TPL Dataflow crea una "maglia" che ha un attore-come si sentono ad essa. RX è più potente ed efficiente ma ha una curva di apprendimento più difficile. Sia TPL Dataflow e Rx hanno metodi async-pronto e funziona bene con codice asincrono.

Solo perché il codice è asincrono non vuol dire che è sicuro. Risorse condivise ancora bisogno di essere protetto, e questo è complicato dal fatto che non vi aspettano all'interno di un blocco. Ecco un esempio di codice asincrono che può danneggiare lo stato condiviso se esso viene eseguito due volte, anche se viene sempre eseguito nello stesso thread:

int value;
Task<int> GetNextValueAsync(int current);
async Task UpdateValueAsync()
{
  value = await GetNextValueAsync(value);
}

Il problema è che il metodo legge il valore e si sospende all'attesa, e quando riprende il metodo presuppone che il valore non è cambiato. Per risolvere questo problema, la classe SemaphoreSlim è stata aumentata con gli overload WaitAsync async-ready. Figura 10 dimostra SemaphoreSlim.WaitAsync.

Figura 10 SemaphoreSlim consente la sincronizzazione asincrona

SemaphoreSlim mutex = new SemaphoreSlim(1);
int value;
Task<int> GetNextValueAsync(int current);
async Task UpdateValueAsync()
{
  await mutex.WaitAsync().ConfigureAwait(false);
  try
  {
    value = await GetNextValueAsync(value);
  }
  finally
  {
    mutex.Release();
  }
}

Codice asincrono viene spesso utilizzato per inizializzare una risorsa che ha quindi memorizzato nella cache e condiviso. Non c'è un tipo incorporato per questo, ma Stephen Toub sviluppato un AsyncLazy <T> che agisce come un'Unione di attività <T> e pigro <T>. Il tipo originale è descritto sul suo blog (bit.ly/dEN178), e una versione aggiornata è disponibile nella mia libreria AsyncEx (nitoasyncex.codeplex.com).

Infine, alcune strutture dati async-pronto a volte sono necessari. TPL Dataflow fornisce un BufferBlock <T> che agisce come una coda di async-pronto produttore/consumatore. In alternativa, AsyncEx fornisce AsyncCollection <T>, che è una versione asincrona di BlockingCollection <T>.

Spero che le linee guida e puntatori in questo articolo sono state utili. Async è una caratteristica lingua veramente impressionante e ora è un grande momento di iniziare ad usarlo!

Stephen Cleary è un programmatore vive nel Michigan settentrionale, padre e marito. Ha lavorato con multithreading e asincrona di programmazione per 16 anni e ha utilizzato il supporto asincrono in Microsoft .NET Framework poiché il primo CTP. Sua home page, tra cui il suo blog, è a stephencleary.com.

Grazie all'esperto tecnica seguente per la revisione di questo articolo: Stephen Toub
Stephen Toub lavora il team di Visual Studio di Microsoft. E ' specializzato nelle aree relative al parallelismo e asincronia.