2014-07-03 6 views
9

Vorrei fare una richiesta a X diversi servizi Web che restituiranno ciascuno true o false.TPL attendere il completamento dell'attività con un valore di ritorno specifico

Queste attività devono essere eseguite in parallelo e mi piacerebbe attendere il primo che completa con un valore vero. Quando ricevo un valore reale, non desidero attendere il completamento degli altri compiti.

Nell'esempio sottostante, t1 non dovrebbe essere atteso dal t3 completa prima e restituisce true:

var t1 = Task.Run<bool>(() => 
{ 
    Thread.Sleep(5000); 
    Console.WriteLine("Task 1 Excecuted"); 
    return true; 
}, cts.Token); 

var t2 = Task.Run<bool>(() => 
{ 
    Console.WriteLine("Task 2 Executed"); 
    return false; 
}, cts.Token); 

var t3 = Task.Run<bool>(() => 
{ 
    Thread.Sleep(2000); 
    Console.WriteLine("Task 3 Executed"); 
    return true; 
}, cts.Token); 

In sostanza sto cercando Task.WhenAny con un predicato, che ovviamente non esiste.

+1

http://stackoverflow.com/questions/14726854/task-parallel-library-waitany-with-specified-result –

+0

@AdamPlocher quella domanda è meno rilevante in un ambiente 'async-await'. – i3arnon

+0

Sento una soluzione TPL DataFlow da qualche parte, vorrei avere il tempo di sedermi e giocarci! –

risposta

16

Si può semplicemente utilizzare Task.WhenAny e un predicato più volte fino a quando l'operazione "giusto" arriva

async Task<T> WhenAny<T>(IEnumerable<Task<T>> tasks, Func<T, bool> predicate) 
{ 
    var taskList = tasks.ToList(); 
    Task<T> completedTask = null; 
    do 
    { 
     completedTask = await Task.WhenAny(taskList); 
     taskList.Remove(completedTask); 
    } while (!predicate(await completedTask) && taskList.Any()); 

    return completedTask == null ? default(T) : await completedTask; 
} 
8

Una possibilità è quella di utilizzare Reactive Extensions. Supponiamo che tu abbia una collezione di compiti. Potrebbe essere i compiti che hai citato nella domanda:

var tasks = new[] { t1, t2, t3 }; 

Per eseguire i compiti in parallelo e il ritorno, quando i primi compiti restituisce true si usa questa espressione:

var result = await tasks 
    .Select(t => t.ToObservable()) 
    .Merge() 
    .FirstOrDefaultAsync(success => success); 

I compiti vengono convertiti in osservabile sequenze che ogni "fuoco" una volta completano l'attività. Queste sequenze vengono quindi unite in una singola sequenza che poi viene "convertita" in qualcosa che può essere atteso usando un predicato. E se necessario, è possibile utilizzare un predicato più complicato anziché success => success.

Dopo questo è possibile annullare i compiti non finiti rimanenti se si utilizza un CancellationTokenSource:

cts.Cancel(); 

La variabile result saranno ora sia true o false e le eventuali attività rimanenti sono state date un segnale per annullare.

Se si desidera testare questo con le attività di esempio si dovrà modificarli un po 'di usare Task.Delay invece di Thread.Sleep per consentire il compito di essere annullato:

var t1 = Task.Run<bool>(async() => 
{ 
    await Task.Delay(TimeSpan.FromSeconds(1), cts.Token); 
    Console.WriteLine("Task 1 Excecuted"); 
    return false; 
}, cts.Token);