2014-04-03 12 views
24

Voglio fare Task.WaitAll() per uscire se una qualsiasi delle attività in esecuzione genera un'eccezione, in modo che non devo aspettare per 60 secondi per finire. Come ottengo un simile comportamento? Se WaitAll() non riesce a raggiungere questo, c'è qualche altra caratteristica o soluzione C#?Come rendere Task.WaitAll() da interrompere se si verifica qualche eccezione?

Task task1 = Task.Run(() => throw new InvalidOperationException()); 
Task task2 = ... 
... 
try 
{ 
    Task.WaitAll(new Task[]{task1, task2, ...}, TimeSpan.FromSeconds(60)); 
} 
catch (AggregateException) 
{ 
    // If any exception thrown on any of the tasks, break out immediately instead of wait all the way to 60 seconds. 
} 
+11

Può uno dei downvoters spiegare? Questo è un requisito ragionevole. – usr

risposta

13

Il seguente dovrebbe farlo senza alterare il codice dei compiti originali (non testati): compiti

static bool WaitAll(Task[] tasks, int timeout, CancellationToken token) 
{ 
    var cts = CancellationTokenSource.CreateLinkedTokenSource(token); 

    var proxyTasks = tasks.Select(task => 
     task.ContinueWith(t => { 
      if (t.IsFaulted) cts.Cancel(); 
      return t; 
     }, 
     cts.Token, 
     TaskContinuationOptions.ExecuteSynchronously, 
     TaskScheduler.Current).Unwrap()); 

    return Task.WaitAll(proxyTasks.ToArray(), timeout, cts.Token); 
} 

nota solo tracce guaste (quelli che hanno gettato). Se avete bisogno di tenere traccia delle attività annullati così, fare questo cambiamento:

if (t.IsFaulted || t.IsCancelled) cts.Cancel(); 

Aggiornato, in attesa sulle deleghe compito è ridondante qui, come ha sottolineato @svick nei commenti. Propone una versione migliorata: https://gist.github.com/svick/9992598.

+1

Perché stai aspettando le attività del proxy? Non sarebbe più semplice attendere le attività originali (mantenendo la cancellazione nella logica di continuazione) [come questo] (https://gist.github.com/svick/9992598)? – svick

+0

@svick, grazie per il punto, nessuna buona ragione per questo. È un avanzo, nella versione iniziale ho usato 'cts = new CancellationTokenSource()' invece di 'CreateLinkedTokenSource' e ho passato il' token' originale 'a' Task.Wait', quindi aveva bisogno dei proxy. Sto aggiornando la risposta. – Noseratio

+0

@Noseratio, perché è necessario 'TaskContinuationOptions.ExecuteSynchronously' e' TaskScheduler.Current'? Inoltre è necessario che i compiti del proxy siano passati a 'cts.Token'? – Tarc

0

Un modo per farlo è utilizzare CancellationTokenSource. Si crea cancellationtokensource e lo si passa come argomento a Task.WaitAll. L'idea è di avvolgere l'attività nel blocco try/catch e, in caso di eccezione, chiamare cancel su cancellationtokensource.

Ecco il codice di esempio

CancellationTokenSource mainCancellationTokenSource = new CancellationTokenSource(); 

       Task task1 = new Task(() => 
       { 
        try 
        { 
         throw new Exception("Exception message"); 
        } 
        catch (Exception ex) 
        { 
         mainCancellationTokenSource.Cancel(); 
        } 

       }, mainCancellationTokenSource.Token); 

       Task task2 = new Task(() => 
       { 
        Thread.Sleep(TimeSpan.FromSeconds(3)); 
        Console.WriteLine("Task is running"); 

       }, mainCancellationTokenSource.Token); 

       task1.Start(); 
       task2.Start(); 

       Task.WaitAll(new[] { task1, task2}, 
          6000, // 6 seconds 
          mainCancellationTokenSource.Token 
          ); 
      } 
      catch (Exception ex) 
      { 
       // If any exception thrown on any of the tasks, break out immediately instead of wait all the way to 60 seconds. 
      } 
1

La classe parallela può fare il lavoro per voi. Puoi utilizzare Parallel.For, ForEach o Invoke.

using System; 
using System.Threading; 
using System.Threading.Tasks; 

namespace Sample_04_04_2014_01 
{ 
    class Program 
    { 
     public static void Main(string[] args) 
     { 
      try 
      { 
      Parallel.For(0,20, i => { 
          Console.WriteLine(i); 
          if(i == 5) 
           throw new InvalidOperationException(); 
          Thread.Sleep(100); 
         }); 
      } 
      catch(AggregateException){} 

      Console.Write("Press any key to continue . . . "); 
      Console.ReadKey(true); 
     } 
    } 
} 

Se uno di questi compiti genera un'eccezione allora nessun altra attività verrà eseguita ad eccezione di quelli la cui esecuzione era già iniziato. Per, ForEach e Invoke sono in attesa che tutte le attività vengano completate prima di riprendere il controllo sul codice chiamante. Puoi utilizzare anche un controllo di grana più fine se usi ParallelLoopState.IsExceptional. Parallel.Invoke è più adatto per il tuo caso.

+0

Aiuta se tutte le attività sono diverse e alcune sono vincolate all'IO, ad es. 'HttpClient.GetStringAsync'? – Noseratio

+0

Parallel.Invoke può ospitare diverse attività. Il chiamante può annullare l'intera operazione se impiega troppo tempo impostando il token di cancellazione nell'argomento ParallelOptions. – mircea

+1

Per non parlare di 'Paralell.For' non supporta facilmente' timeout' (diversamente da 'Task.WaitAll'). Il punto è che dovresti fare qualcosa di simile a un 'httpClient.GetStringAsync() di blocco.Wait (token) 'chiama all'interno del tuo' Parallel.Per' lambda. Che uccide l'idea di avere attività parallele con I/O. 'Paralell.XXX' è utile solo per gli oggetti di lavoro omogenei legati alla CPU. – Noseratio

0

Volevo suggerire una leggera modifica all'eccellente risposta di Noseratio sopra. Nel mio caso avevo bisogno di preservare l'eccezione originale generata, e in un tentativo/cattura circostante distinguere tra stati cancellati ed eccezioni.

public static void WaitUnlessFault(Task[] tasks, CancellationToken token) 
{ 
    var cts = CancellationTokenSource.CreateLinkedTokenSource(token); 

    foreach (var task in tasks) { 
     task.ContinueWith(t => 
     { 
      if (t.IsFaulted) cts.Cancel(); 
     }, 
     cts.Token, 
     TaskContinuationOptions.ExecuteSynchronously, 
     TaskScheduler.Current); 
    } 

    try { 
     Task.WaitAll(tasks, cts.Token); 
    } 
    catch (OperationCanceledException ex) { 
     var faultedTaskEx = tasks.Where(t => t.IsFaulted) 
      .Select(t => t.Exception) 
      .FirstOrDefault(); 

     if (faultedTaskEx != null) 
      throw faultedTaskEx; 
     else 
      throw; 
    } 
} 
Problemi correlati