8

您如何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);
4

3 回答 3

14

您可以使用ContinueWithandWhenAll来等待一个新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 });
于 2012-10-17T10:26:33.760 回答
5

您可以使用一种在它们完成时重新排序它们的方法。这是Jon SkeetStephen Toub描述的一个很好的技巧,我的AsyncEx 库也支持这个技巧。

所有三个实现都非常相似。采取我自己的实现:

/// <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 数据流,那么您可能更喜欢该解决方案。

于 2012-10-17T11:05:17.267 回答
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);
于 2012-10-17T10:27:05.630 回答