wake-up-neo.com

Async Task.WhenAll mit Timeout

Gibt es eine Möglichkeit in der neuen async dotnet 4.5-Bibliothek, ein Timeout für die Task.WhenAll-Methode festzulegen? Ich möchte mehrere Quellen abrufen und nach etwa 5 Sekunden anhalten und die nicht abgeschlossenen Quellen überspringen.

47
broersa

Sie können die resultierende Task mit einer Task.Delay() kombinieren, indem Sie Task.WhenAny() verwenden:

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

Wenn Sie erledigte Aufgaben im Falle eines Timeouts ernten möchten:

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

Ich denke, eine klarere, robustere Option, die auch die Ausnahmebehandlung rechtfertigt wäre, Task.WhenAny für jede Aufgabe zusammen mit einer Timeout-Aufgabe zu verwenden , alle erledigten Aufgaben durchlaufen und die herausfiltern Timeout-Werte und verwenden Sie await Task.WhenAll() anstelle von Task.Result, um alle Ergebnisse zu erfassen.

Hier ist eine vollständige Arbeitslösung:

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

Schauen Sie sich die Abschnitte "Early Bailout" und "Task.Delay" aus dem Task-Based Asynchronous Pattern Overview von Microsoft an.

Frühe Rettungsaktion Eine durch t1 dargestellte Operation kann in einer .__ gruppiert werden. WhenAny mit einer anderen Task t2, und wir können auf die WhenAny-Task warten. t2 könnte ein Timeout oder eine Annullierung oder ein anderes Signal darstellen, das bewirkt, dass die WhenAny-Task vor dem Abschluss von t1 abgeschlossen wird.

9
David Peden

Neben dem Timeout überprüfe ich auch die Stornierung. Dies ist nützlich, wenn Sie eine Web-App erstellen.

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

Was Sie beschreiben, scheint eine sehr verbreitete Forderung zu sein, aber ich konnte nirgendwo ein Beispiel dafür finden. Und ich habe viel gesucht ... Ich habe schließlich folgendes geschaffen:

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

Ich gehe hier von einer Methode SomeTaskAsync aus, die Task <MyResult> zurückgibt.

Von den Mitgliedern von completedTasks sind nur die Aufgaben des Typs MyResult unsere eigenen Aufgaben, bei denen es gelungen ist, die Uhr zu schlagen. Task.Delay gibt einen anderen Typ zurück .. Dies erfordert einen gewissen Kompromiss beim Tippen, funktioniert aber dennoch wunderbar und recht einfach.

(Das Array kann natürlich dynamisch mit einer Abfrage + ToArray erstellt werden).

  • Beachten Sie, dass diese Implementierung nicht erfordert, dass SomeTaskAsync ein Stornotoken erhält.
2
Erez Cohen

Neben der Antwort von svick funktioniert das Folgende für mich, wenn ich warten muss, bis ein paar Aufgaben erledigt sind, aber etwas anderes bearbeiten, während ich warte:

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

Ich kam zu folgendem Code, der das tut, was ich brauchte:

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;
        } 

    }
}

Meine Erklärung ist in meinem Blogpost: http://blog.bekijkhet.com/2012/03/c-async-examples-whenall-whenany.html

0
broersa

Sie können den folgenden Code verwenden:

        var timeoutTime = 10;

        var tasksResult = await Task.WhenAll(
                                listOfTasks.Select(x => Task.WhenAny(
                                    x, Task.Delay(TimeSpan.FromMinutes(timeoutTime)))
                                )
                            );


        var succeededtasksResponses = tasksResult
                                               .OfType<Task<MyResult>>()
                                               .Select(task => task.Result);

        if (succeededtasksResponses.Count() != listOfTasks.Count())
        {
            // Not all tasks were completed
            // Throw error or do whatever you want
        }

        //You can use the succeededtasksResponses that contains the list of successful responses

Wie es funktioniert:

Sie müssen in die Variable timeoutTime das Zeitlimit für die Ausführung aller Aufgaben eingeben. Grundsätzlich warten alle Tasks maximal so lange, wie Sie in timeoutTime festgelegt haben. Wenn alle Aufgaben das Ergebnis zurückgeben, tritt das Zeitlimit nicht auf, und das tasksResult wird festgelegt.

Danach bekommen wir nur die erledigten Aufgaben. Die Aufgaben, die nicht abgeschlossen wurden, haben keine Ergebnisse.

0
Jorge Freitas

Sehen Sie sich einen benutzerdefinierten Taskkombinator an, der in http://tutorials.csharp-online.net/Task_Combinators vorgeschlagen wird.

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
}

Ich habe es noch nicht ausprobiert.

0
Maxim Eliseev

void result version of @ i3arnons Antwort zusammen mit Kommentaren und Änderung des ersten Arguments zur Verwendung dieser Erweiterung.

Ich habe auch eine Weiterleitungsmethode, die das Timeout als int angibt, wobei TimeSpan.FromMilliseconds(millisecondsTimeout) verwendet wird, um andere Task-Methoden zu finden.

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