2012-10-17 12 views
7

Come si avvia più HttpClient.GetAsync() richieste in una sola volta, e loro ogni gestire più presto le loro rispettive risposte tornano? Innanzitutto quello che ho provato è:partire asincrona multiple/attendere funzioni contemporaneamente e gestire separatamente

var response1 = await client.GetAsync("http://example.com/"); 
var response2 = await client.GetAsync("http://stackoverflow.com/"); 
HandleExample(response1); 
HandleStackoverflow(response2); 

Ma ovviamente è ancora sequenziale. Allora ho provato entrambi A partire da subito:

var task1 = client.GetAsync("http://example.com/"); 
var task2 = client.GetAsync("http://stackoverflow.com/"); 
HandleExample(await task1); 
HandleStackoverflow(await task2); 

Ora i compiti sono iniziato allo stesso tempo, che è buono, ma naturalmente il codice ha ancora aspettare per uno dopo l'altro.

Quello che voglio è quello di essere in grado di gestire la risposta "example.com" non appena si entra, e la risposta "stackoverflow.com" non appena si entra.

ho potuto mettere il due attività in un array usano Task.WaitAny() in un ciclo, controllando quale è terminato e chiama il gestore appropriato, ma poi ... come è meglio che i normali vecchi callback? O questo non è davvero un caso d'uso previsto per async/await? In caso contrario, come utilizzerei lo HttpClient.GetAsync() con i callback?

Per chiarire - il comportamento che sto cercando è qualcosa di simile pseudo-codice:

client.GetAsyncWithCallback("http://example.com/", HandleExample); 
client.GetAsyncWithCallback("http://stackoverflow.com/", HandleStackoverflow); 

risposta

12

È possibile utilizzare ContinueWith e WhenAll ad attendere una nuova Task, task1 e Task2 verrà eseguito in parallelo

var task1 = client.GetAsync("http://example.com/") 
        .ContinueWith(t => HandleExample(t.Result)); 

var task2 = client.GetAsync("http://stackoverflow.com/") 
        .ContinueWith(t => HandleStackoverflow(t.Result)); 

var results = await Task.WhenAll(new[] { task1, task2 }); 
+4

FWIW, poiché Task.WhenAll utilizza i parametri nel sovraccarico per prendere un array, è possibile modificare l'ultima riga in Solo quandoTutto (task1, task2) e lasciare che il compilatore generi l'array per te :) http://msdn.microsoft .com/it-it/library/hh194874.aspx –

+1

Questa risposta aspetta solo che entrambi vengano completati. Non "li maneggia entrambi non appena le loro rispettive risposte tornano". –

+2

[So che il commento di StephenCleary è davvero vecchio, ma non volevo che nessun altro pensasse la stessa cosa per essere confuso.] Questa risposta li gestisce ciascuno nelle rispettive chiamate 'ContinueWith' (il cui risultato sono i compiti assegnati a' task1' e 'task2'). L'atteso 'WhenAll' semplicemente si assicura che entrambe le attività di" handlings "vengano eseguite prima che vengano eseguite le righe successive. – patridge

4

dichiarare una funzione asincrona e superare il tuo richiamata in:

void async GetAndHandleAsync(string url, Action<HttpResponseMessage> callback) 
{ 
    var result = await client.GetAsync(url); 
    callback(result); 
} 

E poi basta chiamare più volte:

GetAndHandleAsync("http://example.com/", HandleExample); 
GetAndHandleAsync("http://stackoverflow.com/", HandleStackoverflow); 
5

È possibile utilizzare un metodo che si ri-ordinare loro in quanto completa. Questo è un bel trucco descritto da Jon Skeet e Stephen Toub e supportato anche dal mio AsyncEx library.

Tutte e tre le implementazioni sono molto simili. Prendendo la mia propria implementazione:

/// <summary> 
/// Creates a new array of tasks which complete in order. 
/// </summary> 
/// <typeparam name="T">The type of the results of the tasks.</typeparam> 
/// <param name="tasks">The tasks to order by completion.</param> 
public static Task<T>[] OrderByCompletion<T>(this IEnumerable<Task<T>> tasks) 
{ 
    // This is a combination of Jon Skeet's approach and Stephen Toub's approach: 
    // http://msmvps.com/blogs/jon_skeet/archive/2012/01/16/eduasync-part-19-ordering-by-completion-ahead-of-time.aspx 
    // http://blogs.msdn.com/b/pfxteam/archive/2012/08/02/processing-tasks-as-they-complete.aspx 

    // Reify the source task sequence. 
    var taskArray = tasks.ToArray(); 

    // Allocate a TCS array and an array of the resulting tasks. 
    var numTasks = taskArray.Length; 
    var tcs = new TaskCompletionSource<T>[numTasks]; 
    var ret = new Task<T>[numTasks]; 

    // As each task completes, complete the next tcs. 
    int lastIndex = -1; 
    Action<Task<T>> continuation = task => 
    { 
    var index = Interlocked.Increment(ref lastIndex); 
    tcs[index].TryCompleteFromCompletedTask(task); 
    }; 

    // Fill out the arrays and attach the continuations. 
    for (int i = 0; i != numTasks; ++i) 
    { 
    tcs[i] = new TaskCompletionSource<T>(); 
    ret[i] = tcs[i].Task; 
    taskArray[i].ContinueWith(continuation, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); 
    } 

    return ret; 
} 

è quindi possibile utilizzare come tale:

var tasks = new[] 
{ 
    client.GetAsync("http://example.com/"), 
    client.GetAsync("http://stackoverflow.com/"), 
}; 
var orderedTasks = tasks.OrderByCompletion(); 
foreach (var task in orderedTasks) 
{ 
    var response = await task; 
    HandleResponse(response); 
} 

Un altro approccio è quello di utilizzare TPL Dataflow; come ogni attività completata, inviare il suo funzionamento ad un ActionBlock<T>, qualcosa di simile:

var block = new ActionBlock<string>(HandleResponse); 
var tasks = new[] 
{ 
    client.GetAsync("http://example.com/"), 
    client.GetAsync("http://stackoverflow.com/"), 
}; 
foreach (var task in tasks) 
{ 
    task.ContinueWith(t => 
    { 
    if (t.IsFaulted) 
     ((IDataflowBlock)block).Fault(t.Exception.InnerException); 
    else 
     block.Post(t.Result); 
    }); 
} 

Entrambe le risposte di cui sopra funzionerà bene. Se il resto del codice utilizza/potrebbe usare TPL Dataflow, allora potresti preferire quella soluzione.

+0

Stephen, puoi spiegare perché più attività, quando attese all'interno di un singolo metodo asincrono, sarebbero state completate in ordine piuttosto che non appena completate? per esempio. 'foreach (var in task) attende t;' e assume 't1' completa prima di' t0'; ma il risultato 't1' non verrà mostrato fino a quando il risultato' t0' non viene mostrato per primo. – stt106

+0

Perché 'wait t1' non verrà eseguito fino a quando non si eseguirà il comando' await t0'. –

+0

Ah, penso di sapere perché ero confuso, ma per essere chiari, tutte queste attività sono ancora eseguite in modo asincrono ma i loro risultati sono elaborati in modo sequenziale perché ogni risultato dell'attività diventa la continuazione dell'attività precedente? Vale a dire che il tempo totale necessario per eseguire tutte queste attività è lo stesso della singola attività che costa più tempo, NON la somma di ogni attività in esecuzione. – stt106

Problemi correlati