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);
}
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);
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));
}
}
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");
}
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);
}
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
Utilise the result
foreach (var task in finishedTasks)
{
Console.WriteLine(task.Content.ToString());
}
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
Top comments (0)