Essayer de comprendre la différence entre le TPL & async
/await
en ce qui concerne la création de threads.
Je crois que le TPL (TaskFactory.StartNew
) fonctionne comme ThreadPool.QueueUserWorkItem
en ce qu'il met en file d'attente le travail sur un thread du pool de threads. C'est bien sûr à moins que vous n'utilisiez TaskCreationOptions.LongRunning
qui crée un nouveau thread.
Je pensais que async
/await
fonctionnerait de manière similaire, donc essentiellement:
TPL:
Factory.StartNew( () => DoSomeAsyncWork() )
.ContinueWith(
(antecedent) => {
DoSomeWorkAfter();
},TaskScheduler.FromCurrentSynchronizationContext());
Async
/Await
:
await DoSomeAsyncWork();
DoSomeWorkAfter();
serait identique. D'après ce que j'ai lu, il semble que async
/await
seulement "parfois" crée un nouveau thread. Alors, quand crée-t-il un nouveau fil et quand ne crée-t-il pas un nouveau fil? Si vous aviez affaire à IO ports d'achèvement, je peux voir qu'il n'a pas à créer un nouveau thread mais sinon je pense que ce serait le cas. Je suppose que ma compréhension de FromCurrentSynchronizationContext
toujours était un peu floue aussi. J'ai toujours pensé que c'était essentiellement le fil de l'interface utilisateur.
Je crois que le TPL (TaskFactory.Startnew) fonctionne de manière similaire à ThreadPool.QueueUserWorkItem en ce qu'il met en file d'attente le travail sur un thread dans le pool de threads.
D'après ce que j'ai lu, il semble que async/n'attend que "parfois" crée un nouveau fil.
En fait, ce n'est jamais le cas. Si vous voulez le multithreading, vous devez l'implémenter vous-même. Il existe une nouvelle méthode Task.Run
Qui est juste un raccourci pour Task.Factory.StartNew
, Et c'est probablement la façon la plus courante de démarrer une tâche sur le pool de threads.
Si vous aviez affaire à IO ports d'achèvement, je peux voir qu'il n'a pas à créer un nouveau thread, mais sinon je pense qu'il le devrait.
Bingo. Ainsi, des méthodes comme Stream.ReadAsync
Créeront en fait un wrapper Task
autour d'un IOCP (si le Stream
a un IOCP).
Vous pouvez également créer des "tâches" non E/S, non CPU. Un exemple simple est Task.Delay
, Qui renvoie une tâche qui se termine après un certain temps.
La chose intéressante à propos de async
/await
est que vous pouvez mettre en file d'attente du travail dans le pool de threads (par exemple, Task.Run
), Effectuer certaines opérations liées aux E/S (par exemple, Stream.ReadAsync
), Et effectuez une autre opération (par exemple, Task.Delay
) ... et ce sont toutes des tâches! Ils peuvent être attendus ou utilisés dans des combinaisons comme Task.WhenAll
.
Toute méthode qui renvoie Task
peut être await
ed - il ne doit pas nécessairement s'agir d'une méthode async
. Ainsi, Task.Delay
Et les opérations liées aux E/S utilisent simplement TaskCompletionSource
pour créer et terminer une tâche - la seule chose qui se fait sur le pool de threads est l'achèvement réel de la tâche lorsque l'événement se produit (timeout, Achèvement des E/S, etc.).
Je suppose que ma compréhension de FromCurrentSynchronizationContext a toujours été un peu floue également. J'ai toujours pensé que c'était essentiellement le fil d'interface utilisateur.
J'ai écrit n article sur SynchronizationContext
. La plupart du temps, SynchronizationContext.Current
:
Tout thread peut définir son propre SynchronizationContext
, il y a donc des exceptions aux règles ci-dessus.
Notez que l'attente par défaut Task
planifiera le reste de la méthode async
sur le SynchronizationContext
en cours s'il n'est pas nul ; sinon, il va sur le TaskScheduler
en cours. Ce n'est pas si important aujourd'hui, mais dans un avenir proche, ce sera une distinction importante.
J'ai écrit ma propre async
/await
intro sur mon blog, et Stephen Toub a récemment publié un excellent async
/await
FAQ .
Concernant "simultanéité" vs "multithreading", voir cette question connexe SO question . Je dirais que async
permet la simultanéité, qui peut ou non être multithreadée . Il est facile d'utiliser await Task.WhenAll
Ou await Task.WhenAny
Pour effectuer un traitement simultané, et sauf si vous utilisez explicitement le pool de threads (par exemple, Task.Run
Ou ConfigureAwait(false)
), alors vous pouvez avoir plusieurs opérations simultanées en cours en même temps (par exemple, plusieurs E/S ou d'autres types comme Delay
) - et il n'y a pas de thread nécessaire pour elles. J'utilise le terme "concurrence à un seul thread "pour ce type de scénario, bien que dans un hôte ASP.NET, vous pouvez en fait vous retrouver avec" zéro - concurrence simultanée filetée ". Ce qui est plutôt agréable .
async/wait simplifie fondamentalement les méthodes ContinueWith
(Continuations dans Continuation Passing Style )
Il n'introduit pas de concurrence - vous devez toujours le faire vous-même (ou utiliser la version Async d'une méthode de framework.)
Ainsi, la version C # 5 serait:
await Task.Run( () => DoSomeAsyncWork() );
DoSomeWorkAfter();