Arshad Badar Khan
Arshad Badar Khan

Reputation: 1004

What is the best way to make multiple service calls with async await

As a part of best practices for async and await it is recommended to not use Task.Run. I have a service which makes multiple calls to a third party service and we use async to make those calls. I'm looking for advice on code improvement in the code below.

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


}

Upvotes: 0

Views: 424

Answers (3)

John Wu
John Wu

Reputation: 52260

For a problem like this it is handy to use LINQ. LINQ produces immutable results so you avoid concurrency issues and don't need any specialized collections.

In general, using LINQ or similar programming techniques (i.e. thinking like a functional programmer) will make 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 );
}

Upvotes: 2

Alexander Petrov
Alexander Petrov

Reputation: 14231

Instead of directly creating tasks using the Task.Run method you can use continuations.

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

Thus, the GetRoute method will be executed asynchronously, without creating a separate thread. And the result obtained from it will be processed in a separate thread (task).

However, this is only necessary if the result takes a long time to process. Otherwise, a separate thread is not needed at all.

Upvotes: 0

Hasan
Hasan

Reputation: 1298

As pointed in the comments I would suggest that you could use Task.WhenAll() to determine all task to complete and get the results with return await Task.WhenAll(tasks);. To do that, you can update your code like shown below.

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

}

Since all the calls will return the same type, you do not need to start a second foreach in other loop. Also, this way you will avoid locking thread execution with Task.WaitAll() and your program will run more syncronous. To see the difference between WhenAll() vs WaitAll(), you can check this out.

Upvotes: 0

Related Questions