2015-06-25 23 views
7

Nella mia applicazione sono presenti 3 attività responsabili della ricezione dei dati dai database.
Fino ad ora ho eseguito tutte le attività una dopo l'altra. Se prima avevate finito e avevate Risultato, questi erano i miei dati, se ora avessi iniziato il secondo compito e ho controllato di nuovo.Task.Factory.ContinueWhenQualsiasi operazione continua quando un'operazione termina senza eccezioni

Recentemente ho trovato informazioni che posso avviare più attività e continuare quando una di esse termina con Task.Factory.ContinueWhenAny. Funziona bene se tutte le mie attività non generano eccezioni, ma se qualche attività fallisce non riesco a ottenere i risultati che voglio.

Ad esempio:

var t1 = Task.Factory.StartNew(() => 
{ 
    Thread.Sleep(5000); 
    return 1; 
}); 

var t2 = Task.Factory.StartNew(() => 
{ 
    Thread.Sleep(2000); 
    throw new Exception("My error"); 
    return 2; 
}); 

var t3 = Task.Factory.StartNew(() => 
{ 
    Thread.Sleep(4000); 
    return 3; 
}); 

Task.Factory.ContinueWhenAny(new[] {t1, t2,t3}, (t) => 
{ 
    Console.WriteLine(t.Result); 
}); 

Questo codice inizia 3 compiti e aspetta fino a quando uno di loro finitura. Poiché t2 genera un'eccezione dopo 2 secondi, è disponibile in ContinueWhenAny.

Dal codice sopra vorrei ottenere 3 in t.Result.
Esiste un'opzione per continuare solo quando l'attività è stata completata correttamente? Qualcosa di simile Task.Factory.ContinueWhenAnyButSkipFailedTasks

EDIT 1 Questa è la mia soluzione per ora sulla base di @Nitram risposta:

var t1 = Task.Factory.StartNew(() => 
{ 
    var rnd = new Random(); 
    Thread.Sleep(rnd.Next(5,15)*1000); 
    throw new Exception("My error"); 
    return 1; 
}); 

var t2 = Task.Factory.StartNew(() => 
{ 
    Thread.Sleep(2000); 
    throw new Exception("My error"); 
    return 2; 
}); 

var t3 = Task.Factory.StartNew(() => 
{ 
    throw new Exception("My error"); 
    return 3; 
}); 

var tasks = new List<Task<int>> { t1, t2, t3 }; 

Action<Task<int>> handler = null; 

handler = t => 
{ 
    if (t.IsFaulted) 
    { 
     tasks.Remove(t); 
     if (tasks.Count == 0) 
     { 
      throw new Exception("No data at all!"); 
     } 
     Task.Factory.ContinueWhenAny(tasks.ToArray(), handler); 
    } 
    else 
    { 
     Console.WriteLine(t.Result); 
    } 
}; 

Task.Factory.ContinueWhenAny(tasks.ToArray(), handler); 

Che cosa ho bisogno ora è come gettare un'eccezione quando tutte le attività gettano eccezione?
Forse questo può essere modificato in un unico metodo che restituirebbe compito - qualcosa come attività figlio?

+0

Penso che sia necessario scrivere il proprio Task.WhenQualsiasi versione che richiede un Func che specifica se questo particolare completamento attività deve completare l'WhenAny. – usr

risposta

2

C'è un sovraccarico della funzione ContinueWhenAny che fa ciò che si desidera.

Impostare semplicemente lo TaskContinuationOptions su OnlyOnRanToCompletion e le attività non riuscite verranno ignorate.

Task.Factory.ContinueWhenAny(new[] {t1, t2,t3}, (t) => 
{ 
    Console.WriteLine(t.Result); 
}, TaskContinuationOptions.OnlyOnRanToCompletion); 

Quindi abbiamo concluso che questa risposta è in realtà sbagliata.

La rimozione delle attività da un elenco sembra essere l'unico modo che riesco a pensare. Ho provato a mettere questo in alcune linee di codice. Ecco qui:

var tasks = new List<Task> {t1, t2, t3}; 

Action<Task> handler = null; 
handler = (Task t) => 
{ 
    if (t.IsFauled) { 
     tasks.Remove(t); 
     Task.Factory.ContinueWhenAny(tasks.ToArray, handler); 
    } else { 
     Console.WriteLine(t.Result); 
    } 
}; 
Task.Factory.ContinueWhenAny(tasks.ToArray, handler); 

Io non sono molto fermo con C#, ma spero che ti dia un'idea. In pratica, ciò che sta accadendo è che ogni volta che viene gestita un'operazione che presenta un difetto, questa attività viene rimossa dall'elenco delle attività conosciute e la funzione attende il successivo.

OK e ora il tutto con .NET 4.5 e il modello async - await. await fondamentalmente ti dà la possibilità di registrare ciò che è mai stato scritto dopo l'attesa in una continuazione.

Quindi questo è quasi lo stesso schema con async - await.

var tasks = new List<Task> {t1, t2, t3}; 
while (tasks.Any()) 
{ 
    var finishedTask = await Task.WhenAny(tasks); 
    if (finishedTask.IsFaulted) 
    { 
     tasks.Remove(finishedTask); 
    } 
    else 
    { 
     var result = await finishedTask; 
     Console.WriteLine(result); 
     return; 
    } 
} 

L'unica differenza è che la funzione esterna deve essere una funzione async per quello.Ciò significa che incontrando il primo await la funzione esterna restituirà il Task che contiene la continuazione.

È possibile aggiungere una funzione circostante che blocca fino a quando questa funzione non viene eseguita. Il modello async - await offre la possibilità di scrivere codice asincrono non bloccante che "sembra" semplicemente come codice sincrono.

Inoltre, suggerisco di utilizzare la funzione Task.Run per spawnare le attività anziché lo TaskFactory. In seguito risparmierà alcuni problemi. ;-)

+0

Grazie per una risposta così rapida, ma sfortunatamente ottengo l'errore 'Non è valido escludere specifici tipi di continuazione per la continuazione di più attività. – Misiu

+0

Ah ... ciò accade se non si legge correttamente la documentazione. Spiacente, questa risposta è sbagliata. Perché le opzioni NotOn * e OnlyOn * sono illegali per questa funzione. Poiché è l'unica idea che ho è di mantenere un elenco delle attività che attendi e se la funzione si innesca su un'attività fallita puoi rimuoverla e aspettare il resto. – Nitram

+0

Potresti mostrare qualche codice di esempio? Sarebbe molto utile. – Misiu

3

Se stai usando .NET 4.5, è possibile utilizzare Task.WhenAny di raggiungere facilmente ciò che si vuole:

public async Task<int> GetFirstCompletedTaskAsync() 
{ 
    var tasks = new List<Task> 
    { 
     Task.Run(() => 
     { 
      Thread.Sleep(5000); 
      return 1; 
     }), 
     Task.Run(() => 
     { 
      Thread.Sleep(2000); 
      throw new Exception("My error"); 
     }), 
     Task.Run(() => 
     { 
      Thread.Sleep(4000); 
      return 3; 
     }), 
    }; 

    while (tasks.Count > 0) 
    { 
     var finishedTask = await Task.WhenAny(tasks); 
     if (finishedTask.Status == TaskStatus.RanToCompletion) 
     { 
      return finishedTask 
     } 

     tasks.Remove(finishedTask); 
    } 
    throw new WhateverException("No completed tasks"); 
} 
+0

Ho bisogno di una versione leggermente diversa - ho bisogno di ottenere il primo risultato da tutte le attività, ma solo da quella attività che è stata completata. Se tutti loro sono in colpa, voglio lanciare un'eccezione. – Misiu

+0

Questo ha un runtime quadratico nel numero di task. Anche il consumo di memoria quadratica per le continuazioni. – usr

+0

@Misiu Vedi la mia risposta aggiornata. –

2

E se semplicemente fare questo (almeno ha funzionato per me):

 bool taskFinishedFlag = false; 

     Task t1 = Task.Factory.StartNew(() => { Thread.Sleep(4000); return 1; }); 

     Task t2 = Task.Factory.StartNew(() => { Thread.Sleep(2000); 
               throw new Exception("");return 2; }); 

     Task t3 = Task.Factory.StartNew(() => { Thread.Sleep(4000); return 3; }); 

     Task<int>[] Tasks = new[] { t1, t2, t3 }; 

     for (int i = 0; i < Tasks.Length; i++) 
     { 
      Tasks[i].ContinueWith((t) => 
       { 
        if (taskFinishedFlag) return; 
        taskFinishedFlag = true; 
        Console.WriteLine(t.Result); 
       }, TaskContinuationOptions.NotOnFaulted); 
     }  
+2

Questo è l'approccio migliore. Invece di scrivere sulla console, utilizzare TaskCompletionSource per generare un'attività che può essere attesa. – usr

Problemi correlati