【问题标题】:Proper way to handle a group of asynchronous calls in parallel并行处理一组异步调用的正确方法
【发布时间】:2015-09-04 07:18:39
【问题描述】:

我有一个控制台应用程序,它调用 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);
         }
      }
    }
}

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

【问题讨论】:

  • 你需要用await Task.WhenAll()替换你的循环

标签: c# parallel-processing async-await task


【解决方案1】:

答案可以在 C# Cookbook 中的并发中找到:

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(任务数组);

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 帮我一个忙并将其标记为官方答案。这段代码比我的答案好得多。
    【解决方案3】:

    正如@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);
         }
    

    希望对你有帮助。

    【讨论】:

    • 上述代码的问题是,当我得到结果时,我失去了引用服务的能力。如果您注意到我的原始代码,我会使用返回的响应代码来控制台服务名称。
    • @RHarris 是的,但是您可以轻松地将我的 asyncTasks 列表替换为一个字典,其中该服务将是键,例如 - 这样您就可以确切地知道哪个任务属于哪个服务。我将编辑我的答案以反映该要求。
    【解决方案4】:

    改变这个:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-19
      相关资源
      最近更新 更多