DEV Community

Wai Liu
Wai Liu

Posted on

Making asynchronous API calls in batches using c#

Whether you are calling an API to consume data or doing some web scraping, you may at times run into a situation where you may not want to smash their API too hard which may result in getting your IP or account blocked or just the call to fail.

This article will demonstrate how to break your call up into batches, wait a little bit and then call the next batch.

Example

In this demo example, I have a requirement where I need to call an API 25 times but I don't want to do it all at the same time - I want to make them in batches of 10 asynchronous calls at a time and wait 5 seconds between each batch.

I've created a small console app that does this here so feel free to clone and run it.

Prereq.

You should be able to clone the solution and press f5 to get going. I will mention I'm using a nuget package called RestSharp to make the API calls and I'm calling a website called jsonplaceholder.typicode.com which just gives me demo data.

Craft the queries to be executed

var client = new RestClient("https://jsonplaceholder.typicode.com/comments");

            List<RestRequest> lstRestRequests = new List<RestRequest>();

            // this bit crafts the queries I want to call
            for (int i =0 ; i < 25; i++)
            {

                var request = new RestRequest(Method.GET);
                request.AddQueryParameter("postId", i.ToString());

                lstRestRequests.Add(request);
            }
Enter fullscreen mode Exit fullscreen mode

The above code just goes through and creates the 25 queries I'd like to call and storing it into lstRestRequests

Split up the list

            // this list stores all my results
            List<IRestResponse> finishedTasks = new List<IRestResponse>();

            // split the list of queries into batches of 30 each
            var batchCalls = splitList(lstRestRequests, 10);
Enter fullscreen mode Exit fullscreen mode

Remember that I don't want to call all 25 at once - what I'll do is split them up into multiple lists of 10. In this example, batchCalls will have 3 lists - the first 2 with 10 calls and the last with 5.

        // Splits a list into multiple smaller lists of specified batch size
        private static IEnumerable<List<T>> splitList<T>(List<T> locations, int size = 100)
        {
            for (int i = 0; i < locations.Count; i += size)
            {
                yield return locations.GetRange(i, Math.Min(size, locations.Count - i));
            }
        }
Enter fullscreen mode Exit fullscreen mode

This helper method does the work of breaking up the lists into smaller lists.

Make the calls in batches

foreach (var batch in batchCalls)
            {
                //call each batch of 30 at the same time
                finishedTasks.AddRange(await Task.WhenAll(batch.Select(r => ExecuteRequest(r, client))));

                //after executing the current batch, wait 5 seconds
                await Task.Delay(5000);
                Console.WriteLine("taken time off, ready to go again");
            }
Enter fullscreen mode Exit fullscreen mode

I loop through batchCalls and for each batch, I will make all of the calls at the same time by executing batch.Select(r => ExecuteRequest(r, client)

The line finishedTasks.AddRange(await Task.WhenAll means I will wait till all the calls are finished before adding them all to FinishedTasks for processing later.

Lastly, I have await Task.Delay(5000); which will wait 5 seconds before going to the next batch.

// this method fires off the request
        private static Task<IRestResponse> ExecuteRequest(RestRequest r, RestClient client)
        {
            Console.WriteLine("Calling API " + r.Parameters[0]);
            return client.ExecuteAsync(r);
        }
Enter fullscreen mode Exit fullscreen mode

As reference, the ExecuteRequest method is just calling the API.

Output

When you run it the output should look like this, demonstrating that it's making the call in batches and then waiting a bit before moving onto the next batch

Output

Utilise the result

            foreach (var task in finishedTasks)
            {
                Console.WriteLine(task.Content.ToString());
            }
Enter fullscreen mode Exit fullscreen mode

Once all the batches have run, all the responses will be stored in finishedTasks and then it's really just a matter of looping through and doing whatever you need to do with the data

Discussion (0)