Exécutez deux tâches asynchrones en parallèle et collectez les résultats dans .NET 4.5

116

J'essaie depuis un moment d'obtenir quelque chose que je pensais être simple de travailler avec .NET 4.5

Je veux lancer deux tâches longues en même temps et collecter les
résultats de la meilleure façon C # 4.5 (RTM)

Ce qui suit fonctionne mais je n'aime pas ça parce que:

  • Je veux Sleepêtre une méthode asynchrone pour pouvoir awaitutiliser d'autres méthodes
  • Ça a l'air maladroit avec Task.Run()
  • Je ne pense même pas que cela utilise de nouvelles fonctionnalités linguistiques!

Code de travail:

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Task.Run(() => Sleep(5000));    
    var task2 = Task.Run(() => Sleep(3000));

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for a total of " + totalSlept + " ms");
}

private static int Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    Console.WriteLine("Sleeping for " + ms + " FINISHED");
    return ms;
}

Code non fonctionnel:

Mise à jour: cela fonctionne réellement et c'est la bonne façon de le faire, le seul problème est le Thread.Sleep

Ce code ne fonctionne pas car l'appel à Sleep(5000)lance immédiatement la tâche en cours d'exécution et Sleep(1000)ne s'exécute donc pas tant qu'elle n'est pas terminée. C'est vrai même si Sleepc'est le cas asyncet je n'utilise pas awaitou n'appelle pas .Resulttrop tôt.

J'ai pensé qu'il y avait peut-être un moyen d'obtenir un non-en cours Task<T>d' exécution en appelant une asyncméthode afin que je puisse ensuite appeler Start()les deux tâches, mais je ne peux pas comprendre comment obtenir un en Task<T>appelant une méthode asynchrone.

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Sleep(5000);    // blocks
    var task2 = Sleep(1000);

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
}

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    return ms;
}
Simon_Weaver
la source
note: faire de Go une méthode asynchrone ne fait aucune différence
Simon_Weaver
3
Le blocage se produit à task1.Resultpas à var task1 = Sleep(5000)car votre méthode Sleep sans mot-clé d'attente est synchrone.
Arvis

Réponses:

86

Vous devez utiliser Task.Delay au lieu de Sleep pour la programmation asynchrone, puis utiliser Task.WhenAll pour combiner les résultats de la tâche. Les tâches seraient exécutées en parallèle.

public class Program
    {
        static void Main(string[] args)
        {
            Go();
        }
        public static void Go()
        {
            GoAsync();
            Console.ReadLine();
        }
        public static async void GoAsync()
        {

            Console.WriteLine("Starting");

            var task1 = Sleep(5000);
            var task2 = Sleep(3000);

            int[] result = await Task.WhenAll(task1, task2);

            Console.WriteLine("Slept for a total of " + result.Sum() + " ms");

        }

        private async static Task<int> Sleep(int ms)
        {
            Console.WriteLine("Sleeping for {0} at {1}", ms, Environment.TickCount);
            await Task.Delay(ms);
            Console.WriteLine("Sleeping for {0} finished at {1}", ms, Environment.TickCount);
            return ms;
        }
    }
Softveda
la source
11
C'est une excellente réponse ... mais je pensais que c'était une mauvaise réponse jusqu'à ce que je l'exécute. alors j'ai compris. Il s'exécute vraiment en 5 secondes. L'astuce est de NE PAS attendre les tâches immédiatement, mais plutôt d'attendre sur Task.WhenAll.
Tim Lovell-Smith
114
async Task<int> LongTask1() { 
  ...
  return 0; 
}

async Task<int> LongTask2() { 
  ...
  return 1; 
}

...
{
   Task<int> t1 = LongTask1();
   Task<int> t2 = LongTask2();
   await Task.WhenAll(t1,t2);
   //now we have t1.Result and t2.Result
}
Bart
la source
2
I +1 parce que vous déclarez t1, t2 comme tâche, ce qui est la bonne manière.
Minime
12
Je pense que cette solution nécessite que la méthode Go soit également asynchrone, ce qui signifie qu'elle expose la capacité d'être asynchrone. Si vous vouliez quelque chose de plus comme le cas askers où la Gométhode de l'appelant est synchrone, mais que vous souhaitez effectuer deux tâches indépendantes de manière asynchrone (c'est-à-dire qu'aucune n'a besoin de se terminer avant l'autre, mais que les deux doivent se terminer avant que les exécutions continuent), ce Task.WaitAllserait mieux, et vous ne Pas besoin du mot-clé await, donc pas besoin que la Gométhode appelante soit elle-même asynchrone. Aucune des deux approches n'est meilleure, c'est juste une question de quel est votre objectif.
AaronLS
1
Méthode Void: async void LongTask1() {...}n'a pas de propriété Task.Result. Utilisez la tâche sans T dans ce cas: async Task LongTask1().
Arvis
Je n'ai pas obtenu les résultats de l'une ou l'autre des tâches. Alors je l'ai changé Task<TResult> t1 = LongTask1();et maintenant je reçois t1.Result. <TResult>est le type de retour de votre résultat. Vous aurez besoin d'un return <TResult>dans votre méthode pour que cela fonctionne.
gilu
1
Il peut être utile de mentionner que si vous faites des choses vraiment simples et que vous ne voulez pas de supplément t1et de t2variables, vous pouvez utiliser new Task(...). Par exemple: int int1 = 0; await Task.WhenAll(new Task(async () => { int1 = await LongTask1(); }));. Un problème de cette approche est que le compilateur ne reconnaîtra pas que la variable a été assignée et la traitera comme non assignée si vous ne lui donnez pas de valeur initiale.
Robert Dennis
3

Bien que votre Sleepméthode soit asynchrone, ce Thread.Sleepn'est pas le cas. L'idée générale de l'async est de réutiliser un seul thread, et non de démarrer plusieurs threads. Parce que vous avez bloqué à l'aide d'un appel synchrone à Thread.Sleep, cela ne fonctionnera pas.

Je suppose que Thread.Sleepc'est une simplification de ce que vous voulez réellement faire. Votre implémentation réelle peut-elle être codée en tant que méthodes asynchrones?

Si vous avez besoin d'exécuter plusieurs appels de blocage synchrones, regardez ailleurs je pense!

Richard
la source
merci Richard - oui, cela semble fonctionner comme prévu lorsque j'utilise réellement mon appel de service
Simon_Weaver
alors comment exécuter asynchrone? Je demande que faire beaucoup de commutation de fichiers et d' attente pour le fichier, environ 5 secondes, puis un autre processus, quand je « quand pour tous » , il exécutons d' abord, puis en second lieu , même si je l' ai dit: var x = y(), et non var x=await y()ou y().wait()encore encore attendez jusqu'au bout, et si async ne gère pas cela tout seul, que dois-je faire? Notez que y est décoré avec async, et je m'attends à ce qu'il fasse tout dans le quand tout, pas exactement là où il est assigné, EDIT: je viens de dire à mon partenaire, essayons Task.Factory, et il a dit que cela fonctionnait quand je m'éclate côté de cette classe
deadManN
2

Pour répondre à ce point:

Je veux que Sleep soit une méthode asynchrone afin qu'elle puisse attendre d'autres méthodes

vous pouvez peut-être réécrire la Sleepfonction comme ceci:

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    var task = Task.Run(() => Thread.Sleep(ms));
    await task;
    Console.WriteLine("Sleeping for " + ms + "END");
    return ms;
}

static void Main(string[] args)
{
    Console.WriteLine("Starting");

    var task1 = Sleep(2000);
    var task2 = Sleep(1000);

    int totalSlept = task1.Result +task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
    Console.ReadKey();
}

l'exécution de ce code produira:

Starting
Sleeping for 2000
Sleeping for 1000
*(one second later)*
Sleeping for 1000END
*(one second later)*
Sleeping for 2000END
Slept for 3000 ms
asidis
la source
2

C'est le week - end maintenant!

    public async void Go()
    {
        Console.WriteLine("Start fosterage...");

        var t1 = Sleep(5000, "Kevin");
        var t2 = Sleep(3000, "Jerry");
        var result = await Task.WhenAll(t1, t2);

        Console.WriteLine($"My precious spare time last for only {result.Max()}ms");
        Console.WriteLine("Press any key and take same beer...");
        Console.ReadKey();
    }

    private static async Task<int> Sleep(int ms, string name)
    {
            Console.WriteLine($"{name} going to sleep for {ms}ms :)");
            await Task.Delay(ms);
            Console.WriteLine("${name} waked up after {ms}ms :(";
            return ms;
    }
Arvis
la source
0

Cet article a permis d'expliquer beaucoup de choses. C'est dans le style FAQ.

FAQ sur Async / Await

Cette partie explique pourquoi Thread.Sleepfonctionne sur le même fil d'origine - menant à ma confusion initiale.

Le mot-clé «async» provoque-t-il l'appel d'une méthode à la file d'attente vers ThreadPool? Pour créer un nouveau fil? Lancer une fusée sur Mars?

Non, non. Et non. Voir les questions précédentes. Le mot clé «async» indique au compilateur que «await» peut être utilisé à l'intérieur de la méthode, de sorte que la méthode peut se suspendre à un point d'attente et avoir son exécution reprise de manière asynchrone lorsque l'instance attendue se termine. C'est pourquoi le compilateur émet un avertissement s'il n'y a pas de "waits" à l'intérieur d'une méthode marquée comme "async".

Simon_Weaver
la source