如何一次启动多个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()
,检查哪一个完成并调用适当的处理程序,但那么......这比常规的旧回调更好吗?或者这不是async / await的预期用例吗?如果没有,我如何使用HttpClient.GetAsync()
进行回调?
澄清 - 我所追求的行为就像这个伪代码:
client.GetAsyncWithCallback("http://example.com/", HandleExample);
client.GetAsyncWithCallback("http://stackoverflow.com/", HandleStackoverflow);
答案 0 :(得分:13)
您可以使用ContinueWith
和WhenAll
等待一个新的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 });
答案 1 :(得分:5)
您可以使用在完成时重新排序的方法。这是由Jon Skeet和Stephen 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,那么您可能更喜欢该解决方案。
答案 2 :(得分: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);