Je viens de faire une observation curieuse concernant la Task.WhenAll
méthode, lors de l'exécution sur .NET Core 3.0. J'ai transmis une Task.Delay
tâche simple en tant qu'argument unique à Task.WhenAll
, et je m'attendais à ce que la tâche encapsulée se comporte de manière identique à la tâche d'origine. Mais ce n'est pas le cas. Les continuations de la tâche d'origine sont exécutées de manière asynchrone (ce qui est souhaitable), et les continuations de plusieurs Task.WhenAll(task)
wrappers sont exécutées de manière synchrone l'une après l'autre (ce qui n'est pas souhaitable).
Voici une démonstration de ce comportement. Quatre tâches de travail attendent la fin de la même Task.Delay
tâche, puis poursuivent un calcul lourd (simulé par a Thread.Sleep
).
var task = Task.Delay(500);
var workers = Enumerable.Range(1, 4).Select(async x =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
$" [{Thread.CurrentThread.ManagedThreadId}] Worker{x} before await");
await task;
//await Task.WhenAll(task);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
$" [{Thread.CurrentThread.ManagedThreadId}] Worker{x} after await");
Thread.Sleep(1000); // Simulate some heavy CPU-bound computation
}).ToArray();
Task.WaitAll(workers);
Voici la sortie. Les quatre suites s'exécutent comme prévu dans différents threads (en parallèle).
05:23:25.511 [1] Worker1 before await
05:23:25.542 [1] Worker2 before await
05:23:25.543 [1] Worker3 before await
05:23:25.543 [1] Worker4 before await
05:23:25.610 [4] Worker1 after await
05:23:25.610 [7] Worker2 after await
05:23:25.610 [6] Worker3 after await
05:23:25.610 [5] Worker4 after await
Maintenant, si je commente la ligne await task
et commente la ligne suivante await Task.WhenAll(task)
, la sortie est assez différente. Toutes les continuations s'exécutent dans le même thread, donc les calculs ne sont pas parallélisés. Chaque calcul commence après la fin du précédent:
05:23:46.550 [1] Worker1 before await
05:23:46.575 [1] Worker2 before await
05:23:46.576 [1] Worker3 before await
05:23:46.576 [1] Worker4 before await
05:23:46.645 [4] Worker1 after await
05:23:47.648 [4] Worker2 after await
05:23:48.650 [4] Worker3 after await
05:23:49.651 [4] Worker4 after await
Étonnamment, cela ne se produit que lorsque chaque travailleur attend un emballage différent. Si je définis le wrapper à l'avance:
var task = Task.WhenAll(Task.Delay(500));
... puis await
la même tâche à l'intérieur de tous les travailleurs, le comportement est identique au premier cas (suites asynchrones).
Ma question est: pourquoi cela se produit-il? Qu'est-ce qui provoque l'exécution continue des différents wrappers de la même tâche dans le même thread, de manière synchrone?
Remarque: encapsuler une tâche avec Task.WhenAny
au lieu de Task.WhenAll
résulte dans le même comportement étrange.
Autre observation: je m'attendais à ce que le wrapper à l'intérieur d'un Task.Run
rende les suites asynchrones. Mais ça n'arrive pas. Les suites de la ligne ci-dessous sont toujours exécutées dans le même thread (de manière synchrone).
await Task.Run(async () => await Task.WhenAll(task));
Clarification: les différences ci-dessus ont été observées dans une application console exécutée sur la plate-forme .NET Core 3.0. Sur le .NET Framework 4.8, il n'y a aucune différence entre l'attente de la tâche d'origine ou l'encapsuleur de tâches. Dans les deux cas, les suites sont exécutées de manière synchrone, dans le même thread.
Task.WhenAll
Task.Delay
de 100
en 1000
afin qu'elle ne soit pas terminée lorsqu'elle est await
éditée.
await Task.WhenAll(new[] { task });
t-il si ?