web-dev-qa-db-fra.com

Async Task.WhenAll avec timeout

Existe-t-il un moyen dans la nouvelle bibliothèque asynchrone Dotnet 4.5 de définir un délai d’expiration pour la méthode Task.WhenAll. Je veux aller chercher plusieurs sources et m'arrêter après, disons, 5 secondes et ignorer les sources qui n'étaient pas finies.

47
broersa

Vous pouvez combiner la Task résultante avec une Task.Delay() à l'aide de Task.WhenAny():

await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(timeout));

Si vous souhaitez récolter des tâches terminées en cas de dépassement de délai:

var completedResults =
  tasks
  .Where(t => t.Status == TaskStatus.RanToCompletion)
  .Select(t => t.Result)
  .ToList();
64
svick

Je pense qu'une option plus claire et plus robuste que gère correctement les exceptions consisterait à utiliser Task.WhenAny avec une tâche timeout , passe en revue toutes les tâches terminées et filtre le ceux de délai d'expiration, et utilisez await Task.WhenAll() au lieu de Task.Result pour rassembler tous les résultats.

Voici une solution de travail complète:

static async Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks, TimeSpan timeout)
{
    var timeoutTask = Task.Delay(timeout).ContinueWith(_ => default(TResult));
    var completedTasks = 
        (await Task.WhenAll(tasks.Select(task => Task.WhenAny(task, timeoutTask)))).
        Where(task => task != timeoutTask);
    return await Task.WhenAll(completedTasks);
}
17
i3arnon

Consultez les sections "Early Bailout" et "Task.Delay" dans Présentation du modèle asynchrone basé sur les tâches .

Sauvetage anticipé. Une opération représentée par t1 peut être regroupée dans un WhenAny avec une autre tâche t2, et nous pouvons attendre la tâche WhenAny. t2 pourrait représenter un délai d'attente, ou une annulation, ou un autre signal que La tâche WhenAny sera terminée avant la fin du t1.

9
David Peden

En plus du délai d'attente, je vérifie également l'annulation, ce qui est utile si vous créez une application Web.

public static async Task WhenAll(
    IEnumerable<Task> tasks, 
    int millisecondsTimeOut,
    CancellationToken cancellationToken)
{
    using(Task timeoutTask = Task.Delay(millisecondsTimeOut))
    using(Task cancellationMonitorTask = Task.Delay(-1, cancellationToken))
    {
        Task completedTask = await Task.WhenAny(
            Task.WhenAll(tasks), 
            timeoutTask, 
            cancellationMonitorTask
        );

        if (completedTask == timeoutTask)
        {
            throw new TimeoutException();
        }
        if (completedTask == cancellationMonitorTask)
        {
            throw new OperationCanceledException();
        }
        await completedTask;
    }
}
2
Tony

Ce que vous décrivez semble être une demande très commune, mais je n’ai trouvé aucun exemple de cela. Et j'ai beaucoup cherché ... J'ai finalement créé ce qui suit:

TimeSpan timeout = TimeSpan.FromSeconds(5.0);

Task<Task>[] tasksOfTasks =
{
    Task.WhenAny(SomeTaskAsync("a"), Task.Delay(timeout)),
    Task.WhenAny(SomeTaskAsync("b"), Task.Delay(timeout)),
    Task.WhenAny(SomeTaskAsync("c"), Task.Delay(timeout))
};

Task[] completedTasks = await Task.WhenAll(tasksOfTasks);

List<MyResult> = completedTasks.OfType<Task<MyResult>>().Select(task => task.Result).ToList();

Je suppose ici une méthode SomeTaskAsync qui renvoie la tâche <MyResult>.

Parmi les membres de completedTasks, seules les tâches de type MyResult sont nos propres tâches qui ont réussi à dépasser le temps. Task.Delay retourne un type différent . Cela nécessite un compromis sur la frappe, mais fonctionne toujours très bien et assez simple.

(Le tableau peut bien sûr être construit dynamiquement en utilisant une requête + ToArray).

  • Notez que cette implémentation ne nécessite pas que SomeTaskAsync reçoive un jeton d'annulation.
2
Erez Cohen

En plus de la réponse de svick, les éléments suivants fonctionnent pour moi lorsque je dois attendre quelques tâches mais que je dois traiter autre chose en attendant:

Task[] TasksToWaitFor = //Your tasks
TimeSpan Timeout = TimeSpan.FromSeconds( 30 );

while( true )
{
    await Task.WhenAny( Task.WhenAll( TasksToWaitFor ), Task.Delay( Timeout ) );
    if( TasksToWaitFor.All( a => a.IsCompleted ) )
        break;

    //Do something else here
}
0
Simon Mattes

Je suis arrivé au code suivant qui fait ce dont j'avais besoin:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net.Http;
using System.Json;
using System.Threading;

namespace MyAsync
{
    class Program
    {
        static void Main(string[] args)
        {
            var cts = new CancellationTokenSource();
            Console.WriteLine("Start Main");
            List<Task<List<MyObject>>> listoftasks = new List<Task<List<MyObject>>>();
            listoftasks.Add(GetGoogle(cts));
            listoftasks.Add(GetTwitter(cts));
            listoftasks.Add(GetSleep(cts));
            listoftasks.Add(GetxSleep(cts));

            List<MyObject>[] arrayofanswers = Task.WhenAll(listoftasks).Result;
            List<MyObject> answer = new List<MyObject>();
            foreach (List<MyObject> answers in arrayofanswers)
            {
                answer.AddRange(answers);
            }
            foreach (MyObject o in answer)
            {
                Console.WriteLine("{0} - {1}", o.name, o.Origin);
            }
            Console.WriteLine("Press <Enter>");
            Console.ReadLine();
        } 

        static async Task<List<MyObject>> GetGoogle(CancellationTokenSource cts) 
        {
            try
            {
                Console.WriteLine("Start GetGoogle");
                List<MyObject> l = new List<MyObject>();
                var client = new HttpClient();
                Task<HttpResponseMessage> awaitable = client.GetAsync("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=broersa", cts.Token);
                HttpResponseMessage res = await awaitable;
                Console.WriteLine("After GetGoogle GetAsync");
                dynamic data = JsonValue.Parse(res.Content.ReadAsStringAsync().Result);
                Console.WriteLine("After GetGoogle ReadAsStringAsync");
                foreach (var r in data.responseData.results)
                {
                    l.Add(new MyObject() { name = r.titleNoFormatting, Origin = "google" });
                }
                return l;
            }
            catch (TaskCanceledException)
            {
                return new List<MyObject>();
            }
        }

        static async Task<List<MyObject>> GetTwitter(CancellationTokenSource cts)
        {
            try
            {
                Console.WriteLine("Start GetTwitter");
                List<MyObject> l = new List<MyObject>();
                var client = new HttpClient();
                Task<HttpResponseMessage> awaitable = client.GetAsync("http://search.Twitter.com/search.json?q=broersa&rpp=5&include_entities=true&result_type=mixed",cts.Token);
                HttpResponseMessage res = await awaitable;
                Console.WriteLine("After GetTwitter GetAsync");
                dynamic data = JsonValue.Parse(res.Content.ReadAsStringAsync().Result);
                Console.WriteLine("After GetTwitter ReadAsStringAsync");
                foreach (var r in data.results)
                {
                    l.Add(new MyObject() { name = r.text, Origin = "Twitter" });
                }
                return l;
            }
            catch (TaskCanceledException)
            {
                return new List<MyObject>();
            }
        }

        static async Task<List<MyObject>> GetSleep(CancellationTokenSource cts)
        {
            try
            {
                Console.WriteLine("Start GetSleep");
                List<MyObject> l = new List<MyObject>();
                await Task.Delay(5000,cts.Token);
                l.Add(new MyObject() { name = "Slept well", Origin = "sleep" });
                return l;
            }
            catch (TaskCanceledException)
            {
                return new List<MyObject>();
            }

        } 

        static async Task<List<MyObject>> GetxSleep(CancellationTokenSource cts)
        {
            Console.WriteLine("Start GetxSleep");
            List<MyObject> l = new List<MyObject>();
            await Task.Delay(2000);
            cts.Cancel();
            l.Add(new MyObject() { name = "Slept short", Origin = "xsleep" });
            return l;
        } 

    }
}

Mon explication est dans mon blog: http://blog.bekijkhet.com/2012/03/c-async-examples-whenall-whenany.html

0
broersa

Découvrez un combinateur de tâches personnalisé proposé dans http://tutorials.csharp-online.net/Task_Combinators

async static Task<TResult> WithTimeout<TResult> 
   (this Task<TResult> task, TimeSpan timeout)
 {
   Task winner = await (Task.WhenAny 
      (task, Task.Delay (timeout)));
   if (winner != task) throw new TimeoutException();
   return await task; // Unwrap result/re-throw
}

Je ne l'ai pas encore essayé.

0
Maxim Eliseev

void result version de la réponse de @ i3arnon, avec les commentaires et le premier argument modifié pour utiliser l'extension this.

J'ai également une méthode de transfert spécifiant le délai d'expiration en tant qu'int utilisant TimeSpan.FromMilliseconds(millisecondsTimeout) pour faire correspondre les autres méthodes Task.

public static async Task WhenAll(this IEnumerable<Task> tasks, TimeSpan timeout)
{
  // Create a timeout task.
  var timeoutTask = Task.Delay(timeout);

  // Get the completed tasks made up of...
  var completedTasks =
  (
    // ...all tasks specified
    await Task.WhenAll(tasks

    // Now finish when its task has finished or the timeout task finishes
    .Select(task => Task.WhenAny(task, timeoutTask)))
  )
  // ...but not the timeout task
  .Where(task => task != timeoutTask);

  // And wait for the internal WhenAll to complete.
  await Task.WhenAll(completedTasks);
}
0
kjhf