Voici le code:
Method finished Main finished
Je m'attends à ce que t.Wait ()
attende réellement la fin de la méthode AsyncTest
et la sortie sera:
static async Task Main(string[] args) { var t = new Task(async () => await AsyncTest()); t.Start(); t.Wait(); Console.WriteLine("Main finished"); } private static async Task AsyncTest() { Thread.Sleep(2000); await Task.Delay(2000); Console.WriteLine("Method finished"); }
En réalité, seule la sortie Main est terminée
. Wait () est terminé juste au moment où vous appuyez sur wait Task.Delay (2000)
dans AsyncTest. La même chose se produit si vous remplacez t.Wait ()
par await t
/ await Task.WhenAll (t)
/ Task.WaitAll (t)
.
La solution est de réécrire la méthode en implémentation synchrone ou d'appeler Wait () directement sur AsyncTest (). Cependant, la question est de savoir pourquoi cela fonctionne d'une manière aussi étrange?
P.S. C'est une version simplifiée du code. J'essayais de réaliser une exécution de tâche différée. En réalité, l'objet Task est créé par une partie du programme, puis est exécuté plus tard par une autre partie après une condition spécifique.
UPD: Réécriture de var t = new Task (async () => wait AsyncTest ())
à var t = new Task (() => AsyncTest (). Wait ())
corrige également le problème. Bien que je ne comprends toujours pas pourquoi Task ne fonctionne pas correctement avec async / await inside delegate.
4 Réponses :
N'utilisez pas var task = new Task (...); task.Start ();
, utilisez simplement Task.Run
à la place.
Dans le premier cas, le constructeur Task (Action action)
est utilisé pour que vous n'attendiez pas réellement la méthode async. Si vous inspectez real
C # sans sucre de syntaxe asyn-await, vous verrez AsyncVoidMethodBuilder
. Dans le cas de Task.Run
, vous utilisez Task.Run (Func
pour recevoir des tâches "réelles".
Donc, si vous voulez utiliser le constructeur, vous devez utiliser l'approche des commentaires: nouvelle tâche
.
Comment cela répond-il à la question?
Une citation de @JonSkeet :
Votre méthode async renvoie simplement void, ce qui signifie qu'il n'y a pas de moyen simple de tout ce qui attend qu'il se termine. (Vous devriez presque toujours évitez d'utiliser les méthodes async void. Ils ne sont vraiment disponibles que pour le pour vous abonner à des événements.)
Alors regardez cette ligne de votre code:
static async Task Main(string[] args) { // best way to do it await AsyncTest(); Console.WriteLine("Main finished"); } private static async Task AsyncTest() { // Don't use thread sleep, await task delay is fine // Thread.Sleep(2000); await Task.Delay(2000); Console.WriteLine("Method finished"); }
Regardez la signature des constructeurs de Task
:
public Task(Action action); public Task(Action action, CancellationToken cancellationToken); public Task(Action action, TaskCreationOptions creationOptions); public Task(Action<object> action, object state); public Task(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions); public Task(Action<object> action, object state, CancellationToken cancellationToken); public Task(Action<object> action, object state, TaskCreationOptions creationOptions); public Task(Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions);
Toutes sont des Actions
et comme vous le savez, Action
a un type de retour void
. p >
var t = new Task(async () => await AsyncTest());
Bien que cette réponse ait du sens, vous semblez avoir copié l'explication de la réponse de ce Jon .
@WiktorZychla Oui, le premier paragraphe est de la légende JonSkeet, j'ai modifié la réponse récemment mais j'ai quitté l'ordinateur portable et j'ai oublié d'envoyer les modifications.
vérifiez ceci
public static async Task Main(string[] args) { var t = Task.Factory.StartNew(async () => await AsyncTest()); //t.Start(); t.Result.Wait(); t.Wait(); Console.WriteLine("Main finished"); } private static async Task AsyncTest() { //Thread.Sleep(2000); await Task.Delay(2000); Console.WriteLine("Method finished"); }
et ce lien
La question porte sur: pourquoi le motif var t = new Task (...); t.Wait ();
ne fonctionne pas comme prévu.
Je ne comprends toujours pas pourquoi Task ne fonctionne pas correctement avec async / await inside delegate.
Parce que le constructeur
Task
n'est utilisé que pour créer Déléguer des tâches - c'est-à-dire des tâches qui représentent du code synchrone à exécuter. Comme le code est synchrone, votre lambdaasync
est traité comme un lambdaasync void
, ce qui signifie que l'instanceTask
n'attendra pas de manière asynchroneAsyncTest
pour terminer.Plus précisément, le constructeur de
Task
ne doit jamais, jamais être utilisé dans un code, n'importe où, pour quelque raison que ce soit . Il n'a littéralement aucun cas d'utilisation valide.Un bon remplacement de
Task.Task
estTask.Run
, ce qui fait comprendreasync
lambdas.Dans mon vrai programme, Task a différé son exécution. L'objet de tâche est créé à un endroit, puis plus tard après une condition spécifique exécutée par une autre partie du programme.
Dans ce cas, utilisez un délégué asynchrone . Plus précisément,
Func
.static async Task Main(string[] args) { Func<Task> func = AsyncTest; // Later, when we're ready to run. await func(); Console.WriteLine("Main finished"); } private static async Task AsyncTest() { Thread.Sleep(2000); await Task.Delay(2000); Console.WriteLine("Method finished"); }
N'utilisez pas
var task = new Task (...); task.Start ();
, utilisez simplementTask.Run
à la place.Dans mon vrai programme, Task a différé l'exécution. L'objet de tâche est créé à un endroit, puis plus tard après une condition spécifique exécutée par une autre partie du programme.
Possibilité de duplication de Task.Wait () n'attendant pas la fin de la tâche a>
@ IllidanS4: ça ne ressemble pas, la méthode renvoie correctement une tâche. Il y a quelque chose d'inattendu à propos du
Task.Delay
ici, si vous le commentez (et laissezThread.Sleep
), il semble fonctionner comme prévu.@WiktorZychla Le
AsyncTest
renvoie une tâche, mais leasync () => wait AsyncTest ()
lambda la jette car il s'agit d'uneAction
(void
) lui-même.() => AsyncTest (). Wait ()
le "corrigerait" (ce serait toujours faux à cause duWait ()
).@GSerg: cela ne devrait pas avoir d'importance. Le constructeur n'attend même pas de tâches. C'est la tâche retournée par le constructeur que les OP essaient d'attendre, pas le lambda interne.
@WiktorZychla Oui, l'OP attend la tâche créée par le constructeur, mais cette tâche n'est pas en mesure d'attendre de manière significative son propre argument lambda, donc par extension, l'OP ne peut pas le faire non plus. Vous n'êtes pas censé passer
async () =>
à un constructeur de tâche.@ IllidanS4: cela peut ressembler à un duplicata du lien , mais c'est un peu différent. Je pensais que ça devrait être OK tant que je n'utilise pas async void dans la signature de méthode.
@GSerg: Je le sais, je comprends aussi quel est le problème ici. Je crois que cette phrase de la vôtre Vous n'êtes pas censé passer async () => à un constructeur de tâche devrait être affichée une réponse réelle. Je veux dire, le seul moyen de réaliser ce que OP veut est d'appeler simplement le
AsyncTest
si nécessaire et de leAttendre
, sans l'envelopper dans une autreTâche
.var t1 = new Task (async () => wait AsyncTest ()); var t2 = t1.Unwrap (); t1.Start (); t2.Wait ();
@GSerg vous avez raison. Ce code
var t = Task.Run (async () => await AsyncTest ())
utiliseAsyncTaskMethodBuilder
, mais celui-civar t = new Task (async ( ) => wait AsyncTest ())
utiliseAsyncVoidMethodBuilder
.@PetSerAl Ouais. Remplacez simplement le dernier
Wait
parawait t2
.@mtkachenko: c'est pourquoi j'ai voté pour votre toute première réponse car elle était correcte.
@WiktorZychla Ce n'est peut-être pas le cas.
Task.Run
démarre la tâche immédiatement, l'OP veut apparemment pouvoir avoir une tâche pas encore en cours d'exécution qu'ils peuventDémarrer ()
et attendre plus tard.