Quelle est la différence entre Task.Start / Wait et Async / Await?

207

Il me manque peut-être quelque chose, mais quelle est la différence entre faire:

public void MyMethod()
{
  Task t = Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();
  UpdateLabelToSayItsComplete();
}

public async void MyMethod()
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  UpdateLabelToSayItsComplete();
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}
Jon
la source

Réponses:

395

Il me manque peut-être quelque chose

Vous êtes.

quelle est la difference entre faire Task.Waitet await task?

Vous commandez votre déjeuner au serveur du restaurant. Un moment après avoir donné votre commande, un ami entre et s'assoit à côté de vous et commence une conversation. Vous avez maintenant deux choix. Vous pouvez ignorer votre ami jusqu'à ce que la tâche soit terminée - vous pouvez attendre que votre soupe arrive et ne rien faire d'autre pendant que vous attendez. Ou vous pouvez répondre à votre ami, et lorsque votre ami cesse de parler, le serveur vous apportera votre soupe.

Task.Waitbloque jusqu'à ce que la tâche soit terminée - vous ignorez votre ami jusqu'à ce que la tâche soit terminée. awaitcontinue de traiter les messages dans la file d'attente de messages, et lorsque la tâche est terminée, il met en file d'attente un message qui dit "reprendre là où vous vous étiez arrêté après cette attente". Vous parlez à votre ami, et quand il y a une pause dans la conversation, la soupe arrive.

Eric Lippert
la source
5
@ronag Non, ce n'est pas le cas. Comment aimeriez-vous que l'attente d'un Taskqui prend 10 ms exécute réellement 10 heures Tasksur votre thread, vous bloquant ainsi pendant 10 heures?
svick
62
@StrugglingCoder: Le await opérateur ne fait rien , sauf d' évaluer son opérande puis revenez immédiatement une tâche à l'appelant en cours . Les gens ont cette idée en tête que l'asynchronie ne peut être obtenue que par le déchargement du travail sur les threads, mais c'est faux. Vous pouvez préparer le petit déjeuner et lire le journal pendant que le toast est dans le grille-pain sans embaucher un cuisinier pour regarder le grille-pain. Les gens disent que bien, il doit y avoir un fil - un travailleur - caché à l'intérieur du grille-pain, mais je vous assure que si vous regardez dans votre grille-pain, il n'y a pas de petit gars qui regarde le toast.
Eric Lippert
11
@StrugglingCoder: Alors, qui fait le travail que vous demandez? Peut-être qu'un autre thread fait le travail et que ce thread a été assigné à un CPU, donc le travail est en fait en cours. Peut-être que le travail est effectué par du matériel et qu'il n'y a aucun fil du tout. Mais vous dites sûrement qu'il doit y avoir du fil dans le matériel . Non. Le matériel existe en dessous du niveau des threads. Il n'y a pas besoin de fil! Vous pourriez bénéficier de la lecture de l'article de Stephen Cleary Il n'y a pas de fil.
Eric Lippert
6
@StrugglingCoder: Maintenant, question, supposons qu'il y ait un travail asynchrone en cours et qu'il n'y ait pas de matériel et qu'il n'y ait pas d'autre thread. Comment est-ce possible? Eh bien, supposons que la chose que vous attendiez fasse la queue dans une série de messages Windows , dont chacun fait un peu de travail? Maintenant, que se passe-t-il? Vous retournez le contrôle à la boucle de messages, il commence à extraire des messages de la file d'attente, en faisant un peu de travail à chaque fois, et le dernier travail qui est fait est "exécuter la continuation de la tâche". Pas de fil supplémentaire!
Eric Lippert
8
@StrugglingCoder: Maintenant, pensez à ce que je viens de dire. Vous savez déjà que c'est ainsi que fonctionne Windows . Vous exécutez une série de mouvements de souris et de clics sur les boutons et ainsi de suite. Les messages sont mis en file d'attente, traités à tour de rôle, chaque message entraîne un travail minime et, une fois terminé, le système continue. L'async sur un thread n'est rien de plus que ce à quoi vous êtes déjà habitué: diviser les grandes tâches en petits morceaux, les mettre en file d'attente et exécuter tous les petits bits dans un ordre. Certaines de ces exécutions entraînent la mise en file d'attente d' autres travaux et la vie continue. Un fil!
Eric Lippert
121

Pour démontrer la réponse d'Eric, voici un code:

public void ButtonClick(object sender, EventArgs e)
{
  Task t = new Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();  
  //If you press Button2 now you won't see anything in the console 
  //until this task is complete and then the label will be updated!
  UpdateLabelToSayItsComplete();
}

public async void ButtonClick(object sender, EventArgs e)
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  //If you press Button2 now you will see stuff in the console and 
  //when the long method returns it will update the label!
  UpdateLabelToSayItsComplete();
}

public void Button_2_Click(object sender, EventArgs e)
{
  Console.WriteLine("Button 2 Clicked");
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}
Jon
la source
27
+1 pour le code (il vaut mieux courir une fois que de lire cent fois). Mais l'expression " //If you press Button2 now you won't see anything in the console until this task is complete and then the label will be updated!" est trompeuse. En appuyant sur le bouton avec le t.Wait();gestionnaire d'événements de clic sur le bouton, ButtonClick()il n'est pas possible d'appuyer sur quoi que ce soit, puis de voir quelque chose dans la console et de mettre à jour l'étiquette "jusqu'à ce que cette tâche soit terminée" car l'interface graphique est figée et ne répond pas, c'est-à-dire les clics ou interactions avec l'interface graphique sont PERDUS jusqu'à la fin de la tâche en attente
Gennady Vanin Геннадий Ванин
2
Je suppose qu'Eric suppose que vous avez une compréhension de base de l'API de la tâche. Je regarde ce code et je me dis "yup t.Waitva bloquer sur le thread principal jusqu'à ce que la tâche soit terminée."
The Muffin Man
50

Cet exemple montre très clairement la différence. Avec async / wait, le thread appelant ne se bloquera pas et continuera à s'exécuter.

static void Main(string[] args)
{
    WriteOutput("Program Begin");
    // DoAsTask();
    DoAsAsync();
    WriteOutput("Program End");
    Console.ReadLine();
}

static void DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    t.Wait();
    WriteOutput("3 - Task completed with result: " + t.Result);
}

static async Task DoAsAsync()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    var result = await t;
    WriteOutput("3 - Task completed with result: " + result);
}

static int DoSomethingThatTakesTime()
{
    WriteOutput("A - Started something");
    Thread.Sleep(1000);
    WriteOutput("B - Completed something");
    return 123;
}

static void WriteOutput(string message)
{
    Console.WriteLine("[{0}] {1}", Thread.CurrentThread.ManagedThreadId, message);
}

Sortie DoAsTask:

[1] Début du programme
[1] 1 - Démarrage
[1] 2 - La tâche a commencé
[3] A - A commencé quelque chose
[3] B - A terminé quelque chose
[1] 3 - Tâche terminée avec résultat: 123
[1] Fin du programme

Sortie DoAsAsync:

[1] Début du programme
[1] 1 - Démarrage
[1] 2 - La tâche a commencé
[3] A - A commencé quelque chose
[1] Fin du programme
[3] B - A terminé quelque chose
[3] 3 - Tâche terminée avec résultat: 123

Mise à jour: exemple amélioré en affichant l'ID de thread dans la sortie.

Mas
la source
4
Mais si je le fais: nouvelle tâche (DoAsTask) .Start (); au lieu de DoAsAsync (); j'obtiens la même fonctionnalité, alors où est l'avantage d'attendre ..
omriman12
1
Avec votre proposition, le résultat de la tâche doit être évalué ailleurs, peut-être une autre méthode ou une lambda. L'attente asynchrone rend le code asynchrone plus facile à suivre. C'est juste un améliorateur de syntaxe.
Mas
@Mas je ne comprends pas pourquoi la fin du programme est après A - a commencé quelque chose. De ma compréhension quand il s'agit d'attendre le processus de mot-clé devrait aller immédiatement au contexte principal puis revenir en arrière.
@JimmyJimm D'après ma compréhension, Task.Factory.StartNew fera tourner un nouveau thread pour exécuter DoSomethingThatTakesTime. En tant que tel, il n'y a aucune garantie que la fin du programme ou quelque chose A - Commencé soit exécuté en premier.
RiaanDP
@JimmyJimm: J'ai mis à jour l'exemple pour afficher les ID de thread. Comme vous pouvez le voir, «Fin du programme» et «Quelque chose a commencé» s'exécutent sur des threads différents. Donc, en réalité, l'ordre n'est pas déterministe.
Mas
10

Wait (), provoquera l'exécution de code potentiellement asynchrone de manière synchronisée. attendre ne sera pas.

Par exemple, vous disposez d'une application Web asp.net. UserA appelle / getUser / 1 endpoint. Le pool d'applications asp.net choisira un thread dans le pool de threads (Thread1) et ce thread fera un appel http. Si vous faites Wait (), ce thread sera bloqué jusqu'à la résolution de l'appel http. Pendant qu'il attend, si UserB appelle / getUser / 2, le pool d'applications devra servir un autre thread (Thread2) pour effectuer à nouveau un appel http. Vous venez de créer (enfin, récupéré du pool d'applications en fait) un autre thread sans raison, car vous ne pouvez pas utiliser Thread1, il a été bloqué par Wait ().

Si vous utilisez wait sur Thread1, SyncContext gèrera la synchronisation entre Thread1 et l'appel http. Simplement, il vous avertira une fois l'appel http terminé. Pendant ce temps, si UserB appelle / getUser / 2, vous utiliserez à nouveau Thread1 pour passer un appel http, car il a été libéré une fois que wait a été atteint. Ensuite, une autre demande peut l'utiliser, encore plus. Une fois l'appel http terminé (utilisateur1 ou utilisateur2), Thread1 peut obtenir le résultat et revenir à l'appelant (client). Thread1 a été utilisé pour plusieurs tâches.

Teoman shipahi
la source
9

Dans cet exemple, pas beaucoup, pratiquement. Si vous attendez une tâche qui revient sur un thread différent (comme un appel WCF) ou qui abandonne le contrôle au système d'exploitation (comme File IO), wait utilisera moins de ressources système en ne bloquant pas un thread.

foson
la source
3

Dans l'exemple ci-dessus, vous pouvez utiliser "TaskCreationOptions.HideScheduler" et modifier considérablement la méthode "DoAsTask". La méthode elle-même n'est pas asynchrone, comme cela arrive avec "DoAsAsync" car elle retourne une valeur "Task" et est marquée comme "async", faisant plusieurs combinaisons, c'est ainsi qu'elle me donne exactement la même chose qu'en utilisant "async / wait" :

static Task DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime, TaskCreationOptions.HideScheduler); //<-- HideScheduler do the magic

    TaskCompletionSource<int> tsc = new TaskCompletionSource<int>();
    t.ContinueWith(tsk => tsc.TrySetResult(tsk.Result)); //<-- Set the result to the created Task

    WriteOutput("2 - Task started");

    tsc.Task.ContinueWith(tsk => WriteOutput("3 - Task completed with result: " + tsk.Result)); //<-- Complete the Task
    return tsc.Task;
}
user8545699
la source