2012-03-23 10 views
42

C'è un modo nella nuova libreria async dotnet 4.5 per impostare un timeout sul metodo Task.WhenAll. Voglio recuperare diverse fonti e fermarmi dopo dire 5 secondi e saltare le fonti che non sono state completate.Async Task.WhenAll with timeout

risposta

55

È possibile combinare la risultante Task con un Task.Delay() utilizzando Task.WhenAny():

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

Se si vuole raccogliere le attività completate in caso di un timeout:

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

Questo ha il più upvotes, ma sappiamo se questo è ora un approccio valido per realizzare questo? – TheJediCowboy

+3

@CitadelCSAlum Cosa intendi? Questo codice fa ciò che viene chiesto. Se non mi credi, puoi leggere la documentazione o provarla tu stesso. – svick

+0

Sebbene questa sia la risposta accettata, fa esattamente ciò che è stato descritto nella domanda? Se ho capito bene, se il timeout si verifica prima che tutte le attività siano state completate, non viene ricevuto alcun risultato (anche se alcune delle attività sono state completate). Ho ragione? Stavo cercando qualcosa che consenta di estrarre i risultati da diversi compiti, prendendo solo quelli che hanno superato il timeout, indipendentemente dal fatto che il resto delle attività non sia riuscito a farlo. Vedi la mia risposta qui sotto. –

9

Controllare le sezioni "Early Bailout" e "Task.Delay" da Microsoft Task-Based Asynchronous Pattern Overview.

Primo piano di salvataggio. Un'operazione rappresentata da t1 può essere raggruppata in un WhenAny con un'altra attività t2 e possiamo attendere l'operazione WhenAny. t2 potrebbe rappresentare un timeout, o una cancellazione, o qualche altro segnale che causerà il completamento dell'operazione WhenAny prima del completamento di t1.

+0

Si desidera aggiungere un riepilogo di ciò che dice? – svick

+1

Non sei sicuro del motivo per cui sei tornato su questo post ma il tuo esempio di codice è esattamente quello che descrive il documento (come presumo tu sappia). A richiesta, ho aggiornato la mia risposta con la citazione letterale. –

-1

sembra come il Task. Il sovraccarico WaitAll con il parametro timeout è tutto ciò che serve - se restituisce true, allora sai che sono tutti completati - altrimenti, puoi filtrare su IsCompleted.

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

Penso che queste attività siano tutte iniziate da soli thread e le nuove funzioni asincrone no, ma correggimi se sbaglio. Sto solo iniziando questa nuova roba asincrona. – broersa

+3

'Task.WaitAll()' sta bloccando, quindi non è una buona idea usarlo in C# 5, se puoi evitarlo. – svick

+0

@broersa In primo luogo, penso che tu abbia sbagliato, la relazione tra i thread ei metodi 'Task's o' async' non è così semplice. In secondo luogo, perché dovrebbe essere così importante? – svick

0

sono giunto alla seguente pezzo di codice che fa quello che mi serviva:

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

    } 
} 

La mia spiegazione è nel mio blogpost: http://blog.bekijkhet.com/2012/03/c-async-examples-whenall-whenany.html

0

Partenza un combinatore un'attività personalizzata proposto in 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 
} 

Non ho provato t ancora.

+0

a) Il collegamento è rotto. b) Funziona per un singolo compito, che non è quello che l'OP ha chiesto. – i3arnon

0

Oltre alla risposta di svick, i seguenti lavori per me quando ho dovuto aspettare un paio di compiti da completare, ma hanno per elaborare qualcosa di diverso, mentre io sto aspettando:

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

Quello che descrivi sembra come una domanda molto comune, ma non ho potuto trovare da nessuna parte un esempio di questo. E ho cercato un sacco ... ho finalmente creato il seguente:

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

presumo qui un metodo che restituisce SomeTaskAsync Task <MyResult>.

Dai membri di Task completati, solo le attività di tipo MyResult sono le nostre attività che sono riuscite a battere il tempo. Task.Delay restituisce un tipo diverso. Ciò richiede un po 'di compromesso sulla digitazione, ma funziona comunque magnificamente e abbastanza semplice.

(L'array può, naturalmente, essere costruito dinamicamente utilizzando una query + ToArray).

  • Si noti che questa implementazione non richiede SomeTaskAsync per ricevere un token di cancellazione.
+0

Questo sembra qualcosa che dovrebbe essere incapsulato in un metodo di supporto. – svick

+0

@ErezCohen Ho reso la mia risposta ancora più semplice, se vuoi dare un'occhiata: http://stackoverflow.com/a/25733275/885318 – i3arnon

+1

@ I3arnon - Nice !. Mi piace. –

12

penso che una più chiara opzione, più robusto che anche does exception handling right sarebbe quella di utilizzare Task.WhenAny su ogni attività insieme a un timeout task, passare attraverso tutte le attività completate e filtrare i timeout e utilizzare await Task.WhenAll() invece di Task.Result a raccogliere tutti i risultati.

Ecco una soluzione di lavoro completo:

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

Ci sono due eventi, quando ci sono problemi di prestazioni? Il secondo WhenAll è quello di annullare la chiusura di una attività < >? Puoi spiegare questo per favore? –

+0

@MenelaosVergis Il primo 'Task.WhenAll' viene eseguito su attività che restituiscono attività completate (ad esempio i risultati di' Task.WhenAny's). Quindi filtro queste attività con una clausola where. Finalmente uso 'Task.WhenAll' su queste attività per estrarre i loro risultati effettivi. Tutti questi compiti dovrebbero già essere completati a questo punto. – i3arnon

1

Oltre al timeout, controllo anche la cancellazione che è utile se si sta costruendo una web app.

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