2012-10-17 24 views
7

如何啓動多個HttpClient.GetAsync()請求一次,並且只要他們各自的反應回來處理它們每?首先我試過的是:啓動多個異步/一次等待功能和處理它們分別

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

但當然它仍然是連續的。於是我試着開始他們倆在一次:

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

現在的任務是在同一時間,這是很好的開始,但當然,代碼仍然必須等待一前一後。

我要的是能夠儘快處理「example.com」響應,它有,並儘快談到在「stackoverflow.com」響應。

我可以把兩個任務在一個陣列在一個循環的使用Task.WaitAny(),檢查哪一個成品,並調用相應的處理程序,但後來......怎麼就是不只是普通的舊回調更好?或者,這不是真正意義上的異步/等待用例嗎?如果沒有,我會怎麼用HttpClient.GetAsync()有回調?

爲了澄清 - 我以後的行爲是這樣的僞代碼:

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

回答

12

您可以使用ContinueWithWhenAll等待一個新的Task,TASK1和Task2將並行

執行
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,因爲Task.WhenAll使用PARAMS過載取一個數組,你可以改變的最後一行只WhenAll(TASK1,TASK2),讓編譯器生成數組爲你:) http://msdn.microsoft .COM/EN-US /庫/ hh194874。aspx –

+1

這個答案只是等待他們倆完成。它不會「在各自的反應回來後立即處理它們」。 –

+2

[我知道@ StephenCleary的評論真的很老,但我不想讓其他人想到同樣的事情會感到困惑。]這個答案在他們各自的'ContinueWith'調用中處理它們(其結果是分配給' task1'和'task2')。等待的'WhenAll'只是確保這些「處理」任務都在任何超出它的行被執行之前完成。 – patridge

4

聲明一個異步功能,並通過您的回調:

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

然後只是把它多次:

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

您可以使用,這將重新排序他們,因爲他們完成的方法。這是Jon SkeetStephen Toub描述一個好的技巧,也可以通過我的AsyncEx library支持。

所有三種實現非常相似。以我自己的實現:

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

然後,您可以使用它作爲這樣的:

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

另一種方法是使用TPL Dataflow;每個任務完成後,其後期操作的ActionBlock<T>,這樣的事情:

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

無論是上述的答案將正常工作。如果其餘代碼使用/可以使用TPL Dataflow,那麼您可能更喜歡該解決方案。

+0

Stephen,請您解釋爲什麼在單個異步方法中等待時,多個任務會按順序完成,而不是在完成後立即完成?例如'foreach(任務中的變量)等待t;'並假設't1'在't0'之前完成;但是直到t0結果被顯示出來爲止,t1結果纔會被顯示出來。 – stt106

+0

因爲'await t1'直到'await t0'完成後纔會執行。 –

+0

啊我想我知道爲什麼我很困惑,但要清楚,所有這些任務仍然是異步執行的,但是他們的結果實際上是按順序處理的,因爲每個任務結果都成爲前一個任務的延續?即運行所有這些任務所需的總時間與最耗時的單個任務相同,而不是每個任務運行時間的總和。 – stt106