2
votes

Task.Wait n'attend pas la fin de la méthode asynchrone

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.


14 commentaires

N'utilisez pas var task = new Task (...); task.Start (); , utilisez simplement Task.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


@ 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 laissez Thread.Sleep ), il semble fonctionner comme prévu.


@WiktorZychla Le AsyncTest renvoie une tâche, mais le async () => wait AsyncTest () lambda la jette car il s'agit d'une Action ( void ) lui-même. () => AsyncTest (). Wait () le "corrigerait" (ce serait toujours faux à cause du Wait () ).


@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 le Attendre , sans l'envelopper dans une autre Tâ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 ()) utilise AsyncTaskMethodBuilder , mais celui-ci var t = new Task (async ( ) => wait AsyncTest ()) utilise AsyncVoidMethodBuilder .


@PetSerAl Ouais. Remplacez simplement le dernier Wait par await 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 peuvent Démarrer () et attendre plus tard.


4 Réponses :


-4
votes

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 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 .


1 commentaires

Comment cela répond-il à la question?



0
votes

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());


2 commentaires

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.



-2
votes

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


1 commentaires

La question porte sur: pourquoi le motif var t = new Task (...); t.Wait (); ne fonctionne pas comme prévu.



8
votes

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 lambda async est traité comme un lambda async void , ce qui signifie que l'instance Task n'attendra pas de manière asynchrone AsyncTest 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 est Task.Run , ce qui fait comprendre async 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");
}

0 commentaires