一次启动多个async / await函数并单独处理它们

如何一次启动多个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); 

您可以使用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 }); 

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

这三种实现都非常相似。 采取我自己的实施:

 ///  /// Creates a new array of tasks which complete in order. ///  /// The type of the results of the tasks. /// The tasks to order by completion. public static Task[] OrderByCompletion(this IEnumerable> 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[numTasks]; var ret = new Task[numTasks]; // As each task completes, complete the next tcs. int lastIndex = -1; Action> 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(); 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 ,如下所示:

 var block = new ActionBlock(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,那么您可能更喜欢该解决方案。

声明异步函数并传递回调:

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

然后只需多次调用它:

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