用异步等待进行多个服务调用的最佳方法是什么

时间:2019-02-03 11:01:46

标签: c# .net async-await

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

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


}

3 个答案:

答案 0 :(得分:2)

对于这样的问题,使用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 );
}

答案 1 :(得分:0)

正如评论中指出的那样,我建议您可以使用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

答案 2 :(得分:0)

您可以使用延续来代替使用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方法将异步执行,而无需创建单独的线程。从中获得的结果将在单独的线程(任务)中进行处理。

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