2012-10-17 24 views
7

Jak rozpocząć wiele żądań od razu na jeden i zaraz po ich odpowiedziach? Najpierw próbowałem:Uruchamianie wielu funkcji async/await jednocześnie i obsługa ich osobno

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

Ale oczywiście nadal jest sekwencyjny. Więc próbowałem rozpoczęciem ich obu naraz:

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

Teraz zadania są rozpoczęła w tym samym czasie, co jest dobre, ale oczywiście kod wciąż musi czekać na jeden po drugim.

Co chcę jest aby móc obsłużyć odpowiedź „example.com” tak szybko, jak to jest w, a „stackoverflow.com” odpowiedź tak szybko, jak to jest w.

mogę umieścić dwa zadania w tablicy używają Task.WaitAny() w pętli, sprawdzając, który z nich się zakończył i wywołuje odpowiedni moduł obsługi, ale ... jak to jest lepsze niż zwykłe stare callbacki? Czy to naprawdę nie jest zamierzony przypadek użycia async/czekania? Jeśli nie, w jaki sposób mogę użyć wywołań zwrotnych w trybie HttpClient.GetAsync()?

Dla wyjaśnienia - zachowanie Jestem po to coś jak ten pseudo-kod:

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

Odpowiedz

12

Można użyć ContinueWith i WhenAll poczekać jeden nowy Task, Zadania1 i task2 będą wykonywane równolegle

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, ponieważ Task.WhenAll używa params w przeciążeniu pobierając tablicę, możesz zmienić ostatnią linię na Just WhenAll (task1, task2) i niech kompilator zbuduje dla ciebie tablicę :) http://msdn.microsoft .com/en-us/library/hh194874.aspx –

+1

Ta odpowiedź tylko czeka na ich zakończenie. Nie "traktuje ich tak szybko, jak tylko powrócą ich odpowiedzi". –

+2

[Wiem, że komentarz @ StephenCleary jest naprawdę stary, ale nie chciałem, aby ktokolwiek inny myślał o tym samym, aby się pomylić.] Ta odpowiedź obsługuje je w ich odpowiednich wywołaniach "ContinueWith" (których wynikiem są faktyczne zadania przypisane do ' task1' i 'task2'). Oczekiwane 'WhenAll' po prostu upewnia się, że oba te zadania są wykonywane przed wykonaniem jakichkolwiek linii poza nim. – patridge

4

stwierdzenie funkcję asynchronicznej i zdać zwrotnego w:

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

A potem po prostu nazywają go wielokrotnie:

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

można użyć metody, która będzie zmieniać kolejność ich jako one kompletne. To jest fajna sztuczka opisana przez Jon Skeet i Stephen Toub, a także obsługiwana przez moją AsyncEx library.

Wszystkie trzy implementacje są bardzo podobne. Biorąc własną realizacji:

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

Można następnie używać go jako takie:

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

Innym podejściem jest użycie TPL Dataflow; jak kończy każde zadanie, pisać swoje działanie do ActionBlock<T>, coś takiego:

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

Każda z powyższych odpowiedzi będzie działać dobrze. Jeśli reszta twojego kodu używa/może używać TPL Dataflow, to możesz preferować to rozwiązanie.

+0

Stephen, czy możesz wyjaśnić, dlaczego wiele zadań, oczekiwanych w ramach jednej metody asynchronicznej, zakończyłoby się w kolejności, a nie zaraz po ich zakończeniu? na przykład 'foreach (var t in tasks) waiting t;' i załóżcie, że 't1' kończy się przed' t0'; ale wynik "t1" nie zostanie wyświetlony, dopóki wynik "t0" nie zostanie wyświetlony jako pierwszy. – stt106

+0

Ponieważ 'czekaj na t1' nie zostanie wykonane, dopóki nie zakończy się' czekaj na t0'. –

+0

Ah Myślę, że wiem, dlaczego byłem zdezorientowany, ale po prostu, aby było jasne, wszystkie te zadania są nadal wykonywane asynchronicznie, ale ich wyniki są przetwarzane sekwencyjnie, ponieważ każdy wynik zadania staje się kontynuacją poprzedniego zadania? Mianowicie całkowity czas potrzebny na wykonanie wszystkich tych zadań jest taki sam, jak w przypadku pojedynczego zadania, które kosztuje najwięcej czasu, a NIE sumy czasu wykonywania każdego zadania. – stt106

Powiązane problemy