2013-05-21 16 views
5

According to MSDN, async e await non creare nuove discussioni:asincrona Understanding/attendono senza fili

I async e await parole chiave non causano ulteriori thread da creare.

Con questo in mente, ho difficoltà a capire il flusso di controllo di alcuni programmi semplici. Il mio esempio completo è qui sotto. Si noti che richiede lo Dataflow library, che è possibile installare da NuGet.

using System; 
using System.Threading.Tasks.Dataflow; 

namespace TaskSandbox 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      BufferBlock<int> bufferBlock = new BufferBlock<int>(); 

      Consume(bufferBlock); 
      Produce(bufferBlock); 

      Console.ReadLine(); 
     } 

     static bool touched; 
     static void Produce(ITargetBlock<int> target) 
     { 
      for (int i = 0; i < 5; i++) 
      { 
       Console.Error.WriteLine("Producing " + i); 
       target.Post(i); 
       Console.Error.WriteLine("Performing intensive computation"); 
       touched = false; 
       for (int j = 0; j < 100000000; j++) 
        ; 
       Console.Error.WriteLine("Finished intensive computation. Touched: " + touched); 
      } 

      target.Complete(); 
     } 

     static async void Consume(ISourceBlock<int> source) 
     { 
      while (await source.OutputAvailableAsync()) 
      { 
       touched = true; 
       int received = source.Receive(); 
       Console.Error.WriteLine("Received " + received); 
      } 
     } 
    } 
} 

uscita:

Producing 0 
Performing intensive computation 
Received 0 
Finished intensive computation. Touched: True 
Producing 1 
Performing intensive computation 
Received 1 
Finished intensive computation. Touched: True 
Producing 2 
Performing intensive computation 
Received 2 
Finished intensive computation. Touched: False 
Producing 3 
Performing intensive computation 
Received 3 
Finished intensive computation. Touched: False 
Producing 4 
Performing intensive computation 
Received 4 
Finished intensive computation. Touched: True 

Questo sembra indicare che Consume è dato comando mentre il ciclo for è in esecuzione, come il compito OutputAvailableAsync completa:

for (int j = 0; j < 100000000; j++) 
    ; 

Questo sarebbe sorprendente in un modello filettato. Ma se non sono coinvolti ulteriori thread, come può il controllo di resa Produce nel mezzo del ciclo for?

+0

@ I4V: la risposta a tale domanda afferma che "è necessario che tutte le operazioni di blocco producano esplicitamente il controllo utilizzando il modello async/await". Ma nella mia domanda, il controllo passa da 'Produce' a' Consume' senza 'Produce' che dà un controllo esplicito. Questa è la parte di cui sono confuso. –

+3

@Matthew Poiché questa è un'applicazione Console non c'è 'SynchronizationContext', il che significa che tutti i callback delle chiamate' await' vanno a 'SynchronizationContext.Default', che è il pool di thread, quindi tecnicamente ci sono in realtà due thread in esecuzione a volte durante l'esecuzione di questo programma. Per evitare la creazione di thread aggiuntivi è necessario creare il proprio contesto di sincronizzazione personalizzato e impostarlo. Se lo facessi, vedresti che le chiamate "Recieved" non saranno chiamate fino a quando tutta la produzione non è stata fatta specificatamente perché non stai * producendo controllo mentre produci. – Servy

+0

@Servy: se ci sono più thread coinvolti, perché MSDN afferma che "In particolare, questo approccio è migliore di BackgroundWorker per le operazioni legate all'IO perché ... non è necessario proteggersi dalle condizioni di gara"? Ho modificato il mio esempio per aggiungere una semplice condizione di gara. –

risposta

-1

La gestione del controllo avviene all'interno dello stesso thread, in modo che quando il ciclo è in esecuzione, il metodo Consume non lo sia e viceversa. Se stavi usando i thread, potrebbe non essere necessariamente così, e in effetti ti aspetteresti che entrambi funzionassero contemporaneamente.

Il fatto che siano nello stesso thread non significa in alcun modo che il controllo non possa passare da una parte del codice a un altro. .NET (e ogni altro framework, AFAIK) gestisce in modo fluido, e ogni parte viene eseguita con il proprio contesto senza problemi.

Tuttavia, eseguire entrambe le cose in un thread significa che mentre Consume è in esecuzione, il ciclo si "interrompe". Se lo Consume impiega troppo tempo, è esattamente ciò che un utente potrebbe percepire. Questo è il motivo per cui molti programmatori che non conoscono i moduli di Windows sono sorpresi dal fatto che riempire i controlli della GUI con troppe informazioni contemporaneamente fa sì che i loro moduli si bloccino e talvolta si oscurino: il thread che aggiorna lo schermo è lo stesso in cui viene eseguita la logica di controllo, se non usando thread di background worker.

+0

Che cosa fa passare il controllo a 'Consume' senza 'Produce' che produce un controllo esplicito? .NET nota che 'Produce' è in esecuzione da molto tempo e dà il controllo a 'Consume' (simile al ruolo del sistema operativo in un modello con thread)? –

+0

In realtà penso che Servy abbia una risposta migliore della mia nei commenti. – Renan

2

se non sono coinvolti ulteriori thread, come può Produrre il controllo della resa nel mezzo del ciclo for?

Chi ha detto che non sono coinvolti ulteriori thread? Il fatto che tu abbia dichiarato è stato:

Le parole chiave asincrone e di attesa non causano la creazione di thread aggiuntivi.

Che è assolutamente vero. Il vostro programma include i frammenti

target.Post(i); 

await source.OutputAvailableAsync()) 

La mia ipotesi è che la chiamata a target.Post(i) o source.OutputAvailableAsync() creato un thread.await non produce un thread; tutto il await fa è assegna il resto del metodo come la continuazione dell'attività restituita dalla chiamata e quindi restituisce il controllo al chiamante. Se quell'attività ha generato una discussione per fare il suo lavoro, è affare.

await è solo un altro flusso di controllo; un flusso di controllo molto complicato, per essere sicuri, ma un flusso di controllo tuttavia. Non è uno zucchero sintattico per creare discussioni; è uno zucchero sintattico per assegnare una continuazione a un compito.

+4

In realtà, nessuna di queste chiamate crea thread. @Servy è corretto in quanto l'operatore 'await' (in particolare, il task attenditer utilizzato dal codice generato dall'operatore' await') utilizza il 'defaultContext' predefinito per pianificare le continuazioni del metodo sui thread del pool di thread. –

+0

@StephenCleary: Ah, bene. Grazie per la nota. –