2012-03-23 18 views
42

Gibt es in der neuen asynchronen dotnet 4.5-Bibliothek eine Möglichkeit, eine Zeitüberschreitung für die Methode Task.WhenAll festzulegen. Ich möchte mehrere Quellen holen und nach etwa 5 Sekunden anhalten und die Quellen auslassen, die nicht fertig sind.Async Task.WhenAll mit Zeitüberschreitung

Antwort

55

könnten Sie kombinieren die resultierende Task mit einem Task.Delay() mit Task.WhenAny():

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

Wenn Sie erledigte Aufgaben bei einem Timeout ernten wollen:

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

Dies hat die meisten Upvotes, aber wissen wir, ob dies jetzt ein gültiger Ansatz dafür ist? – TheJediCowboy

+3

@CitadelCSAlum Was meinst du? Dieser Code tut, was gefragt wird. Wenn Sie mir nicht glauben, können Sie die Dokumentation lesen oder selbst ausprobieren. – svick

+0

Obwohl dies die akzeptierte Antwort ist, macht es genau das, was in der Frage beschrieben wurde? Wenn ich richtig verstanden habe, wird, wenn die Zeitüberschreitung auftritt, bevor alle Aufgaben abgeschlossen sind, kein Ergebnis empfangen (selbst wenn einige der Aufgaben abgeschlossen wurden). Hab ich recht? Ich suchte nach etwas, das das Extrahieren von Ergebnissen aus mehreren Aufgaben ermöglicht - und zwar nur mit denen, die das Zeitlimit überschritten haben, unabhängig davon, ob der Rest der Aufgaben fehlgeschlagen ist. Siehe meine Antwort unten. –

9

Überprüfen Sie die Abschnitte "Early Bailout" und "Task.Delay" von Microsoft Task-Based Asynchronous Pattern Overview.

Frühzeitige Rettung. Eine Operation, die durch t1 dargestellt wird, kann in einer WhenAny mit einer anderen Task t2 gruppiert werden, und wir können auf die WhenAny-Task warten. t2 könnte ein Timeout, eine Stornierung oder ein anderes Signal darstellen, das dazu führt, dass die WhenAny-Task vor dem Abschluss von t1 beendet.

+0

Möchten Sie eine Zusammenfassung dessen hinzufügen, was es sagt? – svick

+1

Ich bin mir nicht sicher, warum Sie zu diesem Beitrag zurückgekommen sind, aber Ihr Codebeispiel ist genau das, was in dem Papier beschrieben wird (wie Sie vermutlich wissen). Auf Ihre Anfrage hin habe ich meine Antwort mit dem wörtlichen Zitat aktualisiert. –

-1

scheint, wie die Aufgabe. WaitAll Überladung mit dem Timeout-Parameter ist alles was Sie brauchen - wenn es wahr zurückgibt, dann wissen Sie, dass sie alle abgeschlossen sind - andernfalls können Sie nach IsCompleted filtern.

if (Task.WaitAll(tasks, myTimeout) == false) 
{ 
    tasks = tasks.Where(t => t.IsCompleted); 
} 
... 
+0

Ich denke, diese Aufgaben sind alle in eigenen Threads gestartet und die neuen asynchronen Funktionen sind nicht, aber korrigieren Sie mich, wenn ich falsch liege. Ich beginne gerade dieses neue asynchrone Zeug. – broersa

+3

'Task.WaitAll()' blockiert, so ist es keine gute Idee, es in C# 5 zu verwenden, wenn Sie es vermeiden können. – svick

+0

@broersa Erstens, ich denke du hast das falsch verstanden, die Beziehung zwischen Threads und 'Task's oder' Async' Methoden ist nicht so einfach. Zweitens, warum wäre das wichtig? – svick

0

Ich kam zu dem folgende Stück Code, das tut, was ich brauche:

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

prüft eine benutzerdefinierte Aufgabe combinator in http://tutorials.csharp-online.net/Task_Combinators vorgeschlagen out

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 nicht ich versucht Noch nicht.

+0

a) Die Verbindung ist unterbrochen. b) Dies funktioniert für eine einzelne Aufgabe, was das OP nicht gefragt hat. – i3arnon

0

Neben svick Antwort, die folgenden Werke für mich, wenn ich für ein paar Aufgaben warten müssen vollständig, aber etwas anderes zu verarbeiten, während ich warten werde:

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

Was Sie beschreiben, scheint wie eine sehr häufige Forderung konnte ich jedoch nirgendwo ein Beispiel dafür finden. Und ich suchte eine Menge ... Ich habe schließlich folgendes:

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 nehme hier eine Methode SomeTaskAsync die Aufgabe < MyResult zurück >.

Von den Mitgliedern von completedTasks sind nur Tasks vom Typ MyResult unsere eigenen Aufgaben, die es geschafft haben, die Uhr zu schlagen. Task.Delay gibt einen anderen Typ zurück. Dies erfordert einige Kompromisse beim Tippen, aber funktioniert immer noch schön und ziemlich einfach.

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

  • Beachten Sie, dass diese Implementierung SomeTaskAsync nicht erfordert, um ein Abbruch-Token zu empfangen.
+0

Das sieht wie etwas aus, das in eine Hilfsmethode eingekapselt werden sollte. – svick

+0

@ErezCohen Ich habe meine Antwort noch einfacher gemacht, wenn Sie einen Blick darauf werfen wollen: http://stackoverflow.com/a/25733275/885318 – i3arnon

+1

@ I3arnon - Nice !. Ich mag das. –

12

denke ich, eine klarere, robustere Option, die auch does exception handling right wäre Task.WhenAny auf jede Aufgabe mit einem timeout task zusammen zu verwenden, gehen durch alle erledigten Aufgaben und filtern Sie die Timeout diejenigen aus, und verwenden Sie await Task.WhenAll() statt Task.Result zu sammle alle Ergebnisse.

Hier ist eine komplette 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); 
} 
+0

Gibt es zwei WhenAll, gibt es ein Leistungsproblem? Die zweite WhenAll soll eine Aufgabe < > auspacken? Kannst du das bitte erklären? –

+0

@MenelaosVergis Das erste 'Task.WhenAll' wird für Tasks ausgeführt, die abgeschlossene Tasks zurückgeben (d. H. Die Ergebnisse von' Task.WhenAny's). Dann filtere ich diese Aufgabe mit einer Where-Klausel. Schließlich verwende ich "Task.WhenAll" für diese Aufgaben, um ihre tatsächlichen Ergebnisse zu extrahieren. Alle diese Aufgaben sollten an dieser Stelle bereits abgeschlossen sein. – i3arnon

1

Neben Timeout, habe ich auch die Löschung überprüfen, das nützlich ist, wenn Sie einen Web-App bauen.

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