Je ne comprends pas très bien la différence entre Task.Wait
et await
.
J'ai quelque chose de similaire aux fonctions suivantes dans un service ASP.NET WebAPI:
public class TestController : ApiController
{
public static async Task<string> Foo()
{
await Task.Delay(1).ConfigureAwait(false);
return "";
}
public async static Task<string> Bar()
{
return await Foo();
}
public async static Task<string> Ros()
{
return await Bar();
}
// GET api/test
public IEnumerable<string> Get()
{
Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());
return new string[] { "value1", "value2" }; // This will never execute
}
}
Où Get
sera l'impasse.
Qu'est-ce qui pourrait provoquer cela? Pourquoi cela ne pose-t-il pas un problème lorsque j'utilise une attente de blocage plutôt que await Task.Delay
?
Task.Delay(1).Wait()
ce qui est assez bon.Task.Delay(1).Wait()
est fondamentalement la même chose queThread.Sleep(1000)
. Dans le code de production réel, il est rarement approprié.WaitAll
est à l'origine de l'impasse. Voir le lien vers mon blog dans ma réponse pour plus de détails. Vous devez utiliser à laawait Task.WhenAll
place.ConfigureAwait(false)
un seul appel àBar
Ros
impasse ou ne la bloquerez pas, mais parce que vous avez un énumérable qui en crée plus d'une et attend ensuite toutes, la première barre bloquera la seconde. Si vousawait Task.WhenAll
au lieu d'attendre toutes les tâches, afin de ne pas bloquer le contexte ASP, vous verrez la méthode retourner normalement..ConfigureAwait(false)
tout le long de l'arborescence jusqu'à ce que vous bloquiez, de cette façon, rien n'essaye jamais de revenir au contexte principal; Ça marcherait. Une autre option serait de faire tourner un contexte de synchronisation interne. Lien . Si vous mettez leTask.WhenAll
dans un,AsyncPump.Run
il bloquera efficacement le tout sans que vous ayez besoin deConfigureAwait
quelque part, mais c'est probablement une solution trop complexe.Réponses:
Wait
etawait
- bien que similaires sur le plan conceptuel - sont en fait complètement différents.Wait
bloquera de façon synchrone jusqu'à la fin de la tâche. Ainsi, le thread actuel est littéralement bloqué en attendant la fin de la tâche. En règle générale, vous devez utiliser "async
tout en bas"; c'est-à-dire, ne bloquez pas leasync
code. Sur mon blog, j'entre dans les détails de la façon dont le blocage en code asynchrone provoque un blocage .await
attendra de manière asynchrone jusqu'à la fin de la tâche. Cela signifie que la méthode actuelle est "en pause" (son état est capturé) et la méthode renvoie une tâche incomplète à son appelant. Plus tard, une fois l'await
expression terminée, le reste de la méthode est planifié en tant que continuation.Vous avez également mentionné un "bloc coopératif", par lequel je suppose que vous voulez dire qu'une tâche sur laquelle vous travaillez
Wait
peut s'exécuter sur le thread en attente. Il y a des situations où cela peut arriver, mais c'est une optimisation. Il y a de nombreuses situations où cela ne peut pas se produire, comme si la tâche est pour un autre planificateur, ou si elle est déjà démarrée ou si c'est une tâche non codée (comme dans votre exemple de code:Wait
ne peut pas exécuter laDelay
tâche en ligne car il n'y a pas de code pour ça).Vous pouvez trouver mon
async
/await
intro utile.la source
Wait
fonctionne bien lesawait
blocages.Wait
bloque le thread, et il ne peut pas être utilisé pour d'autres choses.await
code. Soit cela, soit le blocage était sans rapport avec l'un et l'autre et vous avez mal diagnostiqué le problème.SynchronizationContext
, de sorte que le blocage dans une demande ASP.NET Core ne provoque plus de blocage.Sur la base de ce que j'ai lu à partir de différentes sources:
Une
await
expression ne bloque pas le thread sur lequel elle s'exécute. Au lieu de cela, il oblige le compilateur à inscrire le reste de laasync
méthode en tant que continuation de la tâche attendue. Le contrôle revient ensuite à l'appelant de laasync
méthode. Une fois la tâche terminée, il invoque sa poursuite et l'exécution duasync
méthode reprend là où elle s'était arrêtée.Pour attendre la fin d'un single
task
, vous pouvez appeler saTask.Wait
méthode. Un appel à laWait
méthode bloque le thread appelant jusqu'à ce que l'instance de classe unique ait terminé l'exécution. LaWait()
méthode sans paramètre est utilisée pour attendre sans condition la fin d'une tâche. La tâche simule le travail en appelant laThread.Sleep
méthode en veille pendant deux secondes.Cet article est également une bonne lecture.
la source
Certains faits importants n'ont pas été mentionnés dans d'autres réponses:
"async wait" est plus complexe au niveau CIL et coûte donc de la mémoire et du temps CPU.
Toute tâche peut être annulée si le temps d'attente est inacceptable.
Dans le cas où "async wait" nous n'avons pas de gestionnaire pour une telle tâche pour l'annuler ou la surveiller.
L'utilisation de Task est plus flexible que "async wait".
Toute fonctionnalité de synchronisation peut être encapsulée par async.
Je ne vois pas pourquoi je dois vivre avec la méthode de synchronisation et async de duplication de code ou en utilisant des hacks.
la source