Le problème est que vous utilisez la Task
classe non générique , qui n'est pas destinée à produire un résultat. Ainsi, lorsque vous créez l' Task
instance en passant un délégué asynchrone:
Task myTask = new Task(async () =>
... le délégué est traité comme async void
. Un async void
n'est pas un Task
, il ne peut pas être attendu, son exception ne peut pas être gérée, et c'est une source de milliers de questions posées par des programmeurs frustrés ici dans StackOverflow et ailleurs. La solution consiste à utiliser la Task<TResult>
classe générique , car vous souhaitez renvoyer un résultat, et le résultat en est un autre Task
. Vous devez donc créer un Task<Task>
:
Task<Task> myTask = new Task<Task>(async () =>
Maintenant, lorsque vous Start
l'extérieur, Task<Task>
il sera terminé presque instantanément parce que son travail consiste simplement à créer l'intérieur Task
. Vous devrez également attendre l'intérieur Task
. Voici comment cela peut être fait:
myTask.Start();
Task myInnerTask = await myTask;
await myInnerTask;
Vous avez deux alternatives. Si vous n'avez pas besoin d'une référence explicite à l'intérieur, Task
vous pouvez simplement attendre Task<Task>
deux fois l'extérieur :
await await myTask;
... ou vous pouvez utiliser la méthode d'extension intégrée Unwrap
qui combine les tâches externes et internes en une seule:
await myTask.Unwrap();
Ce déballage se produit automatiquement lorsque vous utilisez la Task.Run
méthode beaucoup plus populaire qui crée des tâches à chaud, de sorte que le Unwrap
n'est pas utilisé très souvent de nos jours.
Si vous décidez que votre délégué asynchrone doit renvoyer un résultat, par exemple a string
, vous devez déclarer la myTask
variable comme étant de type Task<Task<string>>
.
Remarque: je n'approuve pas l'utilisation de Task
constructeurs pour créer des tâches froides. Comme une pratique est généralement mal vue, pour des raisons que je ne connais pas vraiment, mais probablement parce qu'elle est utilisée si rarement qu'elle a le potentiel de surprendre d'autres utilisateurs / mainteneurs / réviseurs ignorants du code par surprise.
Conseil général: soyez prudent chaque fois que vous fournissez un délégué asynchrone comme argument d'une méthode. Cette méthode devrait idéalement attendre un Func<Task>
argument (ce qui signifie qu'il comprend les délégués asynchrones), ou au moins un Func<T>
argument (ce qui signifie qu'au moins le généré Task
ne sera pas ignoré). Dans le cas malheureux que cette méthode accepte un Action
, votre délégué sera traité comme async void
. C'est rarement ce que vous voulez, voire jamais.