web-dev-qa-db-fra.com

Différence entre le TPL et asynchrone / attente (gestion des threads)

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.

60
coding4fun

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.

À peu près .

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 awaited - 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:

  • est un contexte d'interface utilisateur si le thread actuel est un thread d'interface utilisateur.
  • est un contexte de demande ASP.NET si le thread en cours traite une demande ASP.NET.
  • est un contexte de pool de threads sinon.

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 .

74
Stephen Cleary

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();
8
Nicholas Butler