【问题标题】:What is the best way to make multiple service calls with async await使用异步等待进行多个服务调用的最佳方法是什么
【发布时间】:2026-02-11 16:00:02
【问题描述】:

作为 async 和 await 最佳实践的一部分,建议不要使用 Task.Run。我有一项服务可以多次调用第三方服务,我们使用异步来进行这些调用。我在下面的代码中寻找关于代码改进的建议。

  public interface IRouteService
{
     Task<IEnumerable<Route>> GetRoute(Coordinates orign, Coordinates destination);
}


public class RouteProvider
{
    private readonly IRouteService _routeService; 

    public RouteProvider(IRouteService routeService)
    {
        _routeService = routeService;
    }

    public async Task<IEnumerable<Route>> GetRoutes(IEnumerable<Coordinates> origns, IEnumerable<Coordinates> destinations)

    {
        ConcurrentBag<Route> routes = new ConcurrentBag<Route>();
        List<Task> tasks = new List<Task>();
        foreach (var origin in origns)
        {
            foreach (var destination in destinations)
            {
                tasks.Add(Task.Run(async () =>
                {
                  var response=  await _routeService.GetRoute(origin, destination);
                    foreach (var item in response)
                    {
                        routes.Add(item);
                    }
                }));
            }
        }
        Task.WaitAll(tasks.ToArray());
        return routes;
    }

}


public class Route
{
    public string Distance { get; set; }

    public Coordinates Origin { get; set; }

    public object Destination { get; set; }

    public string OriginName { get; set; }

    public string DestinationName { get; set; }

}

public class  Coordinates
{
    public float Lat { get; set; }

    public float Long { get; set; }


}

【问题讨论】:

  • 你可以写Task.WaitAll而不是return await Task.WhenAll(tasks);

标签: c# .net async-await


【解决方案1】:

对于这样的问题,使用 LINQ 很方便。 LINQ 产生不可变的结果,因此您可以避免并发问题并且不需要任何专门的集合。

一般来说,使用 LINQ 或类似的编程技术(即像函数式程序员一样思考)将使multithreading much easier

public async Task<IEnumerable<Route>> GetRoutes(IEnumerable<Coordinates> origins, IEnumerable<Coordinates> destinations)
{
    var tasks = origins
        .SelectMany
        (
            o => destinations.Select
            ( 
                d => _routeService.GetRoute(o, d) 
            )
        );
    await Task.WhenAll( tasks.ToArray() );
    return tasks.SelectMany( task => task.Result );
}

【讨论】:

    【解决方案2】:

    正如 cmets 中所指出的,我建议您可以使用 Task.WhenAll() 来确定要完成的所有任务并使用 return await Task.WhenAll(tasks); 获得结果。为此,您可以更新您的代码,如下所示。

    public async Task<IEnumerable<Route>> GetRoutes(IEnumerable<Coordinates> origns, IEnumerable<Coordinates> destinations)
    {
        ConcurrentBag<Route> routes = new ConcurrentBag<Route>();
        List<Task> tasks = new List<Task>();
    
        foreach (var origin in origns)
        {
            foreach (var destination in destinations)
            {
                tasks.Add(_routeService.GetRoute(origin, destination));
            }
        }
    
        var response = await Task.WhenAll(tasks);
    
        foreach (var item in response)
        {
            routes.Add(item);
        }
        return routes;
    }
    

    }

    由于所有调用都将返回相同的类型,因此您无需在其他循环中启动第二个foreach。此外,这样您将避免使用Task.WaitAll() 锁定线程执行,并且您的程序将运行得更加同步。要查看WhenAll()WaitAll() 之间的区别,您可以查看this

    【讨论】:

      【解决方案3】:

      您可以使用延续,而不是使用Task.Run 方法直接创建任务。

      foreach (var origin in origns)
      {
          foreach (var destination in destinations)
          {
              tasks.Add(
                  _routeService.GetRoute(origin, destination)
                      .ContinueWith(response =>
                      {
                          foreach (var item in response.Result)
                              routes.Add(item);
                      })
              );
          }
      }
      

      因此,GetRoute 方法将异步执行,无需创建单独的线程。并且从中得到的结果会在单独的线程(任务)中处理。

      但是,只有在处理结果需要很长时间时才需要这样做。否则,根本不需要单独的线程。

      【讨论】: