2012-10-17 10 views
7

Wie starten Sie mehrere HttpClient.GetAsync()-Anfragen gleichzeitig und behandeln sie alle, sobald ihre jeweiligen Antworten zurückkommen? Zuerst was ich versuchte ist:Mehrere async/await-Funktionen gleichzeitig starten und separat behandeln

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

Aber natürlich ist es immer noch sequentiell. Also habe ich versucht, sie beginnen beide auf einmal:

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

Jetzt werden die Aufgaben sind gestartet zugleich, was gut ist, aber natürlich immer noch der Code für eine nach der anderen warten.

Was ich will, ist in der Lage, die „example.com“ Antwort so schnell zu behandeln, wie es kommt, und die „stackoverflow.com“ Antwort, sobald er kommt.

Ich konnte das setzen zwei Aufgaben in einem Array eine Task.WaitAny() in einer Schleife, zu prüfen, welche beendet und den entsprechenden Handler aufrufen, aber dann ... wie ist das besser als nur normale alte Rückrufe? Oder ist das nicht wirklich ein beabsichtigter Anwendungsfall für async/erwarten? Wenn nicht, wie würde ich HttpClient.GetAsync() mit Rückrufen verwenden?

Um zu klären - das Verhalten, das ich bin nach so etwas wie dieses Pseudo-Code ist:

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

Antwort

12

Sie ContinueWith und WhenAll eine neue verwenden können Task, task1 und task2 abzuwarten wird parallel

ausgeführt werden
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, da Task.WhenAll params in der Überladung verwendet, die ein Array nimmt, können Sie die letzte Zeile in nur WhenAll (task1, task2) ändern und den Compiler das Array für Sie erstellen lassen :) http://msdn.microsoft .com/de-de/library/hh194874.aspx –

+1

Diese Antwort wartet nur auf sie beide zu vervollständigen. Es "behandelt sie nicht jedes Mal, sobald ihre jeweiligen Antworten zurückkommen". –

+2

[Ich weiß @ StephenClearys Kommentar ist wirklich alt, aber ich wollte nicht, dass jemand anders das gleiche denkt, um verwirrt zu werden.] Diese Antwort behandelt sie jeweils in ihren "ContinueWith" -Aufrufen (deren Ergebnis sind die eigentlichen Aufgaben, die 'zugewiesen sind) task1' und 'task2'). Das erwartete 'WhenAll' stellt einfach sicher, dass beide" Handlings "-Tasks ausgeführt werden, bevor irgendwelche Zeilen darüber hinaus ausgeführt werden. – patridge

4

Deklarieren Sie eine Asynchron-Funktion und Ihren Rückruf passiert in:

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

Und dann nennen es einfach mehrmals:

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

Sie können eine Methode verwenden, die sie, wie sie vollständig wird neu sortiert. Dies ist ein netter Trick, der von Jon Skeet und Stephen Toub beschrieben wird und auch von meiner AsyncEx library unterstützt wird.

Alle drei Implementierungen sind sehr ähnlich. meine eigene Implementierung unter:

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

Sie können dann als solche verwendet werden:

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

Ein weiterer Ansatz ist TPL Dataflow zu verwenden; da jede Aufgabe abgeschlossen ist, Posten seinen Betrieb auf eine ActionBlock<T>, etwa wie folgt:

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

Jede der oben genannten Antworten in Ordnung arbeiten werden. Wenn der Rest Ihres Codes TPL Dataflow verwendet/verwenden könnte, bevorzugen Sie möglicherweise diese Lösung.

+0

Stephen, können Sie bitte erklären, warum mehrere Aufgaben, wenn sie innerhalb einer einzigen asynchronen Methode abgewartet werden, in der Reihenfolge abgeschlossen werden, anstatt sobald sie abgeschlossen sind? z.B. 'foreach (var t in Aufgaben) erwarte t;' und nehme an, dass 't1' vor 't0' abgeschlossen ist; aber "t1" Ergebnis wird nicht angezeigt, bis "t0" Ergebnis zuerst angezeigt wird. – stt106

+0

Weil 'wait t1' erst ausgeführt wird, nachdem 'wait t0' abgeschlossen ist. –

+0

Ah Ich denke, ich weiß, warum ich verwirrt war, aber nur um klar zu sein, all diese Aufgaben werden immer noch asynchron ausgeführt, aber ihre Ergebnisse werden tatsächlich sequentiell verarbeitet, weil jedes Aufgabenergebnis die Fortsetzung der vorherigen Aufgabe wird? Die Gesamtzeit, die benötigt wird, um alle diese Aufgaben auszuführen, ist die gleiche wie die einzelne Aufgabe, die die meiste Zeit kostet, NICHT die Summe der Laufzeit jeder Aufgabe. – stt106

Verwandte Themen