web-dev-qa-db-fra.com

Les nouveaux mots clés C # 5.0 'async' et 'wait' utilisent-ils plusieurs cœurs?

Deux nouveaux mots clés ajoutés au langage C # 5.0 sont async et wait , qui fonctionnent tous deux de pair pour exécuter une méthode C # de manière asynchrone sans bloquer le thread appelant.

Ma question est la suivante: ces méthodes tirent-elles réellement avantage de plusieurs cœurs et s'exécutent-elles en parallèle ou la méthode async s'exécute-t-elle dans le même noyau de thread que l'appelant?

60
Icemanind

Deux nouveaux mots clés ajoutés au langage C # 5.0 sont asynchrones et attendent, qui fonctionnent tous deux de pair pour exécuter une méthode C # de manière asynchrone sans bloquer le thread appelant.

Cela atteint le but de la fonctionnalité, mais cela donne trop de "crédit" à la fonctionnalité asynchrone/attente.

Permettez-moi d'être très, très clair sur ce point: await ne provoque pas comme par magie une méthode synchrone à s'exécuter de manière asynchrone. Il ne démarre pas un nouveau thread et exécute la méthode sur le nouveau fil, par exemple. La méthode que vous appelez doit être celle qui sait s'exécuter de manière asynchrone. La façon dont il choisit de le faire est son affaire.

Ma question est la suivante: ces méthodes tirent-elles réellement avantage de plusieurs cœurs et s'exécutent-elles en parallèle ou la méthode async s'exécute-t-elle dans le même noyau de thread que l'appelant?

Encore une fois, c'est entièrement à la méthode que vous appelez. Tout ce que await fait, c'est de demander au compilateur de réécrire la méthode dans un délégué qui peut être passé en tant que continuation de la tâche asynchrone. Autrement dit, la await FooAsync() signifie "appeler FooAsync() et tout ce qui revient doit être quelque chose qui représente l'opération asynchrone qui vient de démarrer. Dites à cette chose que quand elle sait que l'opération asynchrone est fait, il devrait appeler ce délégué. " Le délégué a la propriété que lorsqu'elle est invoquée, la méthode actuelle semble reprendre "là où elle s'était arrêtée".

Si la méthode que vous appelez les horaires fonctionne sur un autre thread affinité à un autre noyau, tant mieux. S'il démarre un minuteur qui envoie un ping à un gestionnaire d'événements à l'avenir sur le thread d'interface utilisateur, tant mieux. await s'en fiche. Tout ce qu'il fait, c'est s'assurer que lorsque le travail asynchrone est terminé, le contrôle peut reprendre là où il s'était arrêté.

Une question que vous n'avez pas posée mais que vous devriez probablement poser est:

Lorsque la tâche asynchrone est terminée et que le contrôle reprend là où il s'était arrêté, l'exécution est-elle dans le même thread qu'avant?

Ça dépend du contexte. Dans une application Winforms où vous attendez quelque chose du thread d'interface utilisateur, le contrôle reprend à nouveau sur le thread d'interface utilisateur. Dans une application console, peut-être pas.

96
Eric Lippert

Eric Lippert a une excellente réponse; Je voulais juste décrire un peu plus le parallélisme async.

L'approche simple "série" est où vous await juste une chose à la fois:

static void Process()
{
  Thread.Sleep(100); // Do CPU work.
}

static async Task Test()
{
  await Task.Run(Process);
  await Task.Run(Process);
}

Dans cet exemple, la méthode Test mettra en file d'attente Process vers le pool de threads et lorsqu'elle se terminera, elle mettra de nouveau en file d'attente Process dans le pool de threads. La méthode Test se terminera après environ 200 ms. À tout moment, un seul thread fait vraiment avancer la progression.

Un moyen simple de paralléliser ceci est d'utiliser Task.WhenAll:

static void Process()
{
  Thread.Sleep(100); // Do CPU work.
}

static async Task Test()
{
  // Start two background operations.
  Task task1 = Task.Run(Process);
  Task task2 = Task.Run(Process);

  // Wait for them both to complete.
  await Task.WhenAll(task1, task2);
}

Dans cet exemple, la méthode Test met en file d'attente Process deux fois dans le pool de threads, puis attend que les deux se terminent. La méthode Test se terminera après environ 100 ms.

Task.WhenAll (et Task.WhenAny) ont été introduits avec async/await pour prendre en charge le parallélisme simple. Cependant, le TPL est toujours là si vous avez besoin de quelque chose de plus avancé (le vrai traitement parallèle lié au CPU est mieux adapté au TPL). TPL joue bien avec async/await.

Je couvre le parallélisme de base async dans mon vers async article de blog , ainsi que le "contexte" auquel Eric a fait allusion.

67
Stephen Cleary

Une méthode async renvoie un objet attendable (un qui a une méthode GetAwaiter), et le compilateur peut générer du code pour consommer cet objet si vous appelez la méthode avec le mot clé await. Vous êtes également libre d'appeler une telle méthode sans le mot-clé wait et de consommer l'objet explicitement.

L'objet encapsule une action asynchrone, qui peut ou non s'exécuter sur un autre thread. L'article d'Eric Lippert Asynchrony in C # 5.0 part Four: It's not magic considère un exemple de programmation asynchrone qui n'implique qu'un seul thread.

4
phoog

Puisque async et await sont basés sur le TPL, ils devraient fonctionner de manière très similaire. Par défaut, vous devez les traiter comme s'ils s'exécutaient sur un thread distinct.

2
Kendall Frey