并行处理一组异步调用的正确方法



我有一个控制台应用程序,它调用 Web API 并获取服务列表。 然后,它会循环访问并调用每个服务。

我有以下几点:

static int Main(string[] args)
{
   ...       
   Task.WaitAll(Process());
}
private static async Task BeginProcess()
{
   using(HttpClientHandler handler = new HttpClientHandler())
   {
      handler.UseDefaultCredentials = true;
      using(var client = new HttpClient(handler))
      {
         var response = client.GetStringAsync(_baseUrl);
         List<Service> services = new List<Service>();
         services = jss.Deserialize<List<Service>>(response.Result);
         client.Timeout = new TimeSpan(0,3,0);
         foreach(var service in services)
         {
            Console.WriteLine("Running " + service.Name);
            var _serviceResponse = await client.PostAsync(_baseURL + service.Id.ToString(), null);
            Console.WriteLine(service.Name + " responded with " + _serviceRepsonse.StatusCode);
         }
      }
    }
}

遗憾的是,此代码按顺序处理每个服务,而不是并行进行调用。 问题是,我不确定如何使这些调用并行运行。

答案可以在 C# 说明书中的并发中找到:

static async Task<string> DownloadAllAsync(IEnumerable<string> urls){
    var httpClient = new HttpClient();
    var downloads = urls.select(url => httpClient.getStringAsync(url));
    Task<string>[] tasks = downloads.ToArray(); //-> tasks are started
    //now that you have an array of tasks you can wait for them all to finish
    string[] htmlPages = await Task.WhenAll(tasks);
    return string.Concat(htmlPages); 
}

这里的关键点是:

  • 安排代码获取任务数组(不同的容器也可以,不一定是数组)
  • 使用 await Task.WhenAll(任务数组);

虽然我已经接受了答案,但我终于找到了这个我觉得更简洁的答案。

await Task.WhenAll(services.Select(async s => {
   Console.WriteLine("Running " + s.Name);
   var _serviceResponse = await client.PostAsync(...);
   Console.WriteLine(s.Name + " responded with " + _serviceResponse.StatusCode);
}));
正如

@SLaks提到的,你需要用这些行替换你的循环......

     var asyncTasks = new Dictionary<Service, Task>();
     foreach(var service in services)
     {
        Console.WriteLine("Running " + service.Name);
       asyncTasks.Add(service, client.PostAsync(_baseURL + service.Id.ToString(), null));
     }
     // All tasks are running, so wait for all of them to finish here
     await Task.WhenAll(asyncTasks);
     foreach(var service in asyncTasks.Keys) {
        Console.WriteLine("Service " + service.Name + " returned " + syncTasks[service].Result);
     }

希望对您有所帮助。

更改以下内容:

foreach(var service in services)
{
    Console.WriteLine("Running " + service.Name);
    var _serviceResponse = await client.PostAsync(_baseURL + service.Id.ToString(), null);
    Console.WriteLine(service.Name + " responded with " + _serviceRepsonse.StatusCode);
}

对此:

var serviceCallTaskList = new List<Task<HttpResponseMessage>>();
foreach(var service in services)
{
    Console.WriteLine("Running " + service.Name);
    serviceCallTaskList.Add(client.PostAsync(_baseURL + service.Id.ToString(), null));
}
HttpResponseMessage[] responseArray = await Task.WhenAll(serviceCallTaskList);
for(int i = 0; i < responseArray.Length; i++)
{
    Console.WriteLine(services[i].Name + " responded with " + responseArray[i].StatusCode);
}

最新更新