Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to use concurrency when calling an API

Inside a c# project I'm making some calls to a web api, the thing is that I'm doing them within a loop in a method. Usually there are not so many but even though I was thinking of taking advantage of parallelism.

What I am trying so far is

public void DeployView(int itemId, string itemCode, int environmentTypeId)
{
    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(ConfigurationManager.AppSettings["ApiUrl"]);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        var agents = _agentRepository.GetAgentsByitemId(itemId);

        var tasks = agents.Select(async a =>
            {
                var viewPostRequest = new
                    {
                        AgentId = a.AgentId,
                        itemCode = itemCode,
                        EnvironmentId = environmentTypeId
                    };

                var response = await client.PostAsJsonAsync("api/postView", viewPostRequest);
            });

        Task.WhenAll(tasks);
    }
}

But wonder if that's the correct path, or should I try to parallel the whole DeployView (i.e. even before using the HttpClient)

Now that I see it posted, I reckon I can't just remove the variable response as well, just do the await without setting it to any variable

Thanks

like image 380
mitomed Avatar asked Jul 13 '15 08:07

mitomed


1 Answers

Usually there is no need to parallelize the requests - one thread making async requests should be enough (even if you have hundreds of requests). Consider this code:

var tasks = agents.Select(a =>
        {
            var viewPostRequest = new
                {
                    AgentId = a.AgentId,
                    itemCode = itemCode,
                    EnvironmentId = environmentTypeId
                };

            return client.PostAsJsonAsync("api/postView", viewPostRequest);
        });
    //now tasks is IEnumerable<Task<WebResponse>>
    await Task.WhenAll(tasks);
    //now all the responses are available
    foreach(WebResponse response in tasks.Select(p=> p.Result))
    {
        //do something with the response
    }

However, you can utilize parallelism when processing the responses. Instead of the above 'foreach' loop you may use:

Parallel.Foreach(tasks.Select(p=> p.Result), response => ProcessResponse(response));

But TMO, this is the best utilization of asynchronous and parallelism:

var tasks = agents.Select(async a =>
        {
            var viewPostRequest = new
                {
                    AgentId = a.AgentId,
                    itemCode = itemCode,
                    EnvironmentId = environmentTypeId
                };

            var response = await client.PostAsJsonAsync("api/postView", viewPostRequest);
            ProcessResponse(response);
        });
await Task.WhenAll(tasks);   

There is a major difference between the first and last examples: In the first one, you have one thread launching async requests, waits (non blocking) for all of them to return, and only then processing them. In the second example, you attach a continuation to each Task. That way, every response gets processed as soon as it arrives. Assuming the current TaskScheduler allows parallel (multithreaded) execution of Tasks, no response remains idle as in the first example.

*Edit - if you do decide to do it parallel, you can use just one instance of HttpClient - it's thread safe.

like image 84
shay__ Avatar answered Oct 05 '22 23:10

shay__