2012-04-23 11 views
50

Cercando di comprendere la differenza tra il TPL & asincrono/attendo quando si tratta della creazione del thread.Differenza tra TPL e async/await (Gestione thread)

Credo che TPL (TaskFactory.Startnew) funzioni in modo simile a ThreadPool.QueueUserWorkItem in quanto accoda il lavoro su un thread nel pool di thread. Ovviamente, a meno che non si usi TaskCreationOptions.LongRunning che crea un nuovo thread.

ho pensato asincrona/await funzionerebbe simile così essenzialmente:

TPL:

Factory.StartNew(() => DoSomeAsyncWork()) 
.ContinueWith( 
    (antecedent) => { 
     DoSomeWorkAfter(); 
    },TaskScheduler.FromCurrentSynchronizationContext()); 

asincrono/Await:

await DoSomeAsyncWork(); 
DoSomeWorkAfter(); 

sarebbe identico. Da quello che sto leggendo sembra asincrono/attendo solo "qualche volta" crea un nuovo thread. Quindi quando crea un nuovo thread e quando non crea un nuovo thread? Se avessi a che fare con le porte di completamento dell'IO posso vederlo non dover creare un nuovo thread, ma altrimenti penserei che sarebbe necessario. Suppongo che la mia comprensione di FromCurrentSynchronizationContext sia sempre stata un po 'confusa. Ho sempre capito che era, in sostanza, il thread dell'interfaccia utente.

+2

In realtà, TaskCreationOptions.LongRunning non garantisce un "nuovo thread". Per MSDN, * l'opzione "LongRunning" fornisce solo un suggerimento allo scheduler; non garantisce un thread dedicato. * L'ho trovato nel modo più duro. – eduncan911

+0

@ eduncan911 anche se quello che dici sulla documentazione è corretto, ho cercato il codice sorgente TPL qualche tempo fa e sono abbastanza sicuro che in realtà un nuovo thread dedicato viene sempre creato quando è specificato 'TaskCreationOptions.LongRunning'. –

+0

@ZaidMasud: potresti voler dare un'altra occhiata.So che stava raggruppando i thread perché 'Thread.CurrentThread.IsThreadPoolThread' restituiva true per thread a esecuzione ridotta di poche centinaia di millisecondi. per non parlare delle variabili ThreadStatic che stavo usando emorragia in più thread, causando ogni sorta di havok. Ho dovuto forzare il mio codice a nuovi thread multipli(), il vecchio modo, per garantire un thread dedicato. In altre parole, non potrei usare la TaskFactory per i thread dedicati. Opzionalmente, potresti implementare il tuo 'TaskScheduler' che restituisce sempre un thread dedicato. – eduncan911

risposta

64

credo che il TPL (TaskFactory.Startnew) funziona in modo simile a ThreadPool.QueueUserWorkItem in che ho t accoda il lavoro su un thread nel pool di thread.

Pretty much.

Da quello che ho letto sembra che async/attenda solo "qualche volta" crea un nuovo thread.

In realtà, non lo fa mai. Se vuoi il multithreading, devi implementarlo tu stesso. C'è un nuovo metodo Task.Run che è solo una scorciatoia per Task.Factory.StartNew ed è probabilmente il modo più comune di avviare un'attività nel pool di thread.

Se si trattava di porte di completamento IO, è possibile vederlo non dover creare un nuovo thread, ma altrimenti penserei che sarebbe necessario.

Bingo. Pertanto, metodi come Stream.ReadAsync creeranno effettivamente un wrapper Task attorno a un IOCP (se lo Stream dispone di un IOCP).

È inoltre possibile creare alcune "attività" non di I/O, senza CPU. Un semplice esempio è Task.Delay, che restituisce un'attività che termina dopo un certo periodo di tempo.

La cosa più cool async/await è che si può coda po 'di lavoro al pool di thread (per esempio, Task.Run), fare un po' mi funzionamento/O-bound (ad esempio, Stream.ReadAsync), e fare qualche altra operazione (ad esempio, Task.Delay) ... e sono tutte attività! Possono essere attesi o utilizzati in combinazioni come Task.WhenAll.

Qualsiasi metodo che restituisce Task può essere await ed - non deve essere un metodo async. Pertanto, le operazioni Task.Delay e I/O utilizzano solo TaskCompletionSource per creare e completare un'attività: l'unica cosa che viene eseguita nel pool di thread è il completamento effettivo dell'attività quando si verifica l'evento (timeout, completamento dell'I/O, ecc.).

Suppongo che la mia comprensione di FromCurrentSynchronizationContext sia sempre stata un po 'confusa. Ho sempre capito che era, in sostanza, il thread dell'interfaccia utente.

Ho scritto an article su SynchronizationContext. La maggior parte delle volte, SynchronizationContext.Current:

  • è un contesto dell'interfaccia utente se il thread corrente è un thread dell'interfaccia utente.
  • è un contesto di richiesta ASP.NET se il thread corrente sta servendo una richiesta ASP.NET.
  • è altrimenti un contesto di pool di thread.

Qualsiasi discussione può impostare il proprio SynchronizationContext, quindi non ci sono eccezioni alle regole di cui sopra.

noti che l'awaiter predefinita Task programmerà il resto del metodo async sulla corrente SynchronizationContextse non è nullo; altrimenti va sull'attuale TaskScheduler. Oggi non è così importante, ma nel prossimo futuro sarà una distinzione importante.

Ho scritto il mio async/await intro sul mio blog e Stephen Toub ha pubblicato di recente un eccellente async/await FAQ.

Per "concorrenza" o "multithreading", vedere this related SO question. Direi che async abilita la concorrenza, che può essere o non essere multithread. È facile utilizzare await Task.WhenAll o await Task.WhenAny per eseguire l'elaborazione simultanea e, a meno che non si utilizzi esplicitamente il pool di thread (ad esempio, Task.Run o ConfigureAwait(false)), è possibile avere contemporaneamente più operazioni simultanee in corso (ad esempio, più I/O o altro tipi come Delay) - e non c'è nessun thread necessario per loro. Io uso il termine "concorrenza a thread singolo" per questo tipo di scenario, anche se in un host ASP.NET, si può effettivamente finire con "zero -threaded concurrency". Che è piuttosto dolce

+1

Bella risposta. Consiglierei anche http://www.infoq.com/articles/Async-API-Design e questa eccellente presentazione: http://channel9.msdn.com/Events/TechEd/Europe/2013/DEV-B318. – Philippe

+0

Il primo link è morto. –

+0

@FelipeDeveza Risolto, grazie! –

8

asincrone/attendere semplifica sostanzialmente le ContinueWith metodi (continuazioni in Continuation Passing Style)

non introduce concorrenza - si devono ancora farlo da soli (o utilizzare la versione asincrona di un metodo quadro.)

così, la versione C# 5 sarebbe:

await Task.Run(() => DoSomeAsyncWork()); 
DoSomeWorkAfter(); 
+0

quindi, dove è in esecuzione DoSomeAsyncWork (async/wait version) nel mio esempio sopra? Se è in esecuzione sul thread dell'interfaccia utente come non lo blocca? – coding4fun

+1

L'esempio di attesa non verrà compilato se 'DoSomeWorkAsync()' restituisce nulla o qualcosa che non è attendibile. Dal tuo primo esempio, ho assunto che si tratta di un metodo sequenziale che si desidera eseguire su un thread diverso. Se lo hai cambiato per restituire un 'Task', senza introdurre la concorrenza, allora si bloccherebbe. Nel senso che si eseguirà in modo sequenziale e sarà proprio come un normale codice sul thread dell'interfaccia utente. 'await' restituisce solo i rendimenti se il metodo restituisce un attendibile che non è ancora stato completato. –

+0

beh, non direi che funziona ovunque scelga di correre. Hai utilizzato Task.Run per eseguire il codice in DoSomeAsyncWork, quindi in questo caso il tuo lavoro verrà eseguito su un thread del thread. –

Problemi correlati