2014-06-26 8 views
13

Task<T> detiene in modo ordinato un calcolo "ha iniziato, potrebbe essere finito", che può essere composto con altre attività, mappato con funzioni, ecc. Al contrario, la monade F # async contiene un calcolo "potrebbe iniziare più tardi, potrebbe essere in esecuzione ora" , insieme a a CancellationToken. In C#, in genere è necessario collegare il CancellationToken a tutte le funzioni che funzionano con Task. Perché il team C# ha scelto di completare il calcolo nella monade Task, ma non nello CancellationToken?Perché non è incluso un CancelToken nell'attività <T> monad?

+2

In C#, è possibile eseguire il calcolo del * "potrebbe iniziare in un secondo momento, potrebbe essere in esecuzione ora", insieme a un'opzione CancelToken * sotto forma di 'var task = new Task (action, token)'. – Noseratio

risposta

9

Più o meno, hanno incapsulato l'uso implicito di CancellationToken per i metodi C# async. Considerate questo:

var cts = new CancellationTokenSource(); 
cts.Cancel(); 
var token = cts.token; 

var task1 = new Task(() => token.ThrowIfCancellationRequested()); 
task1.Start(); 
task1.Wait(); // task in Faulted state 

var task2 = new Task(() => token.ThrowIfCancellationRequested(), token); 
task2.Start(); 
task2.Wait(); // task in Cancelled state 

var task3 = (new Func<Task>(async() => token.ThrowIfCancellationRequested()))(); 
task3.Wait(); // task in Cancelled state 

per un non-async lambda, ho dovuto associare esplicitamente token con la task2 per l'annullamento di propagare correttamente, fornendo come argomento per new Task() (o Task.Run). Per un lambda async utilizzato con task3, si verifica automaticamente come parte del codice dell'infrastruttura async/await.

Inoltre, qualsiasitoken si propaga cancellazione di un metodo async, mentre per non asincrone computazionale new Task()/Task.Run lambda deve essere token stesso passato al costruttore compito o Task.Run.

Ovviamente, dobbiamo ancora chiamare token.ThrowIfCancellationRequested() manualmente per implementare il modello di annullamento della cooperativa. Non riesco a capire perché i team C# e TPL abbiano deciso di implementarlo in questo modo, ma immagino che mirassero a non complicare eccessivamente la sintassi di async/await ma mantenerlo sufficientemente flessibile.

Per quanto riguarda F #, non ho esaminato il codice IL generato del flusso di lavoro asincrono, illustrato in blog post di Tomas Petricek collegato. Tuttavia, per quanto ho capito, il token viene automaticamente testato solo in alcune posizioni del flusso di lavoro, quelle corrispondenti a await in C# (per analogia, potremmo chiamare token.ThrowIfCancellationRequested() manualmente dopo ogni await in C#). Ciò significa che qualsiasi lavoro associato alla CPU non verrà annullato immediatamente. In caso contrario, F # dovrebbe emettere token.ThrowIfCancellationRequested() dopo ogni istruzione IL, che sarebbe un notevole overhead.

+0

Quando e perché si desidera utilizzare async/attendere il lavoro con CPU? – GregC

+1

@GregC, ogni volta che devo eseguire il lavoro con CPU in un'app dell'interfaccia utente: 'var pi = attendi Task.Run (() => CalcPi (cifre, token), token)'. – Noseratio

+1

@GregC Come forma di parallelizzazione. 'aspetta Task.WhenAll (tasks)'. – Aron

2

Inizialmente è stata eseguita un'attività per contenere funzionalità aggiuntive nella classe, ma in seguito è stata modificata per aggregare un oggetto con supporto di funzionalità aggiuntive. Tutto nel nome della performance.

http://blogs.msdn.com/b/pfxteam/archive/2011/11/10/10235962.aspx (vedere "Ristrutturazione Attività" nel documento) Il documento di Joseph E. Hoag fornisce interessanti approfondimenti sulle ottimizzazioni fatte in .NET 4.5. Credo che sarebbe una lettura utile per chiunque cerchi di spremere l'ultimo 10% delle prestazioni da asincrono/attesa.

Presumo che un processo di pensiero simile è stato applicato quando si decide come pacchettizzare la funzionalità di cancellazione.

Non riesco a parlare per il team C# o BCL, ma presumo che si tratti di un ottimizzazione delle prestazioni che è possibile solo nel compilatore F #, o che le prestazioni non erano rilevanti per il team F #. SRP, baby!

+1

Mi ero dimenticato di quella carta Hoag. Sembra che il drive fosse mantenere l'oggetto Task il più piccolo possibile, quindi non portarsi dietro il token di cancellazione, ma invece portarlo in giro nella macchina a stati generata da async/await. –