2

I'm kinda new to async and await. Whenever I call API repeatedly I get a too many request error. So my question is who do I loop through an API call using Async and Await so I don't get penalized? See following code example.

Task <IDList> ListOfIDs = new Task(GetIDListAsycn);
var result  = ListOfIDs.Wait()

Once I get the results, I want to loop through them and get the ID to pass to the API URL like this until I ran out of IDs:

foreach(var n in List) { client.BaseUrl (new Uri("http://api/users/n.ID"))};

Problem is like I said whenever I attempt this kind of logic I get too many request because the client.GetStringAsync is called so many times in short period of time.

PinoyDev
  • 949
  • 1
  • 10
  • 21
  • 2
    await Task.Delay – Steve Jan 26 '18 at 18:54
  • 1
    The best way is to create a class with one function which does your basic operation. Create a new instance of that class in your for loop and call that method. – RICKY KUMAR Jan 26 '18 at 19:00
  • It's still gonna be the same result , you're still looping through that – PinoyDev Jan 26 '18 at 19:03
  • 1
    who is developing that API? Is it you? If yes then create API which comma separated ids and return the list of object – RICKY KUMAR Jan 26 '18 at 19:47
  • Hi Ricky, no I'm not the one creating the API this is from Zoom, I just can't figure out how to loop through it without hitting the "too many request error" somehow I need to iterate thru the Id as a parameter for the api url if that make sense – PinoyDev Jan 26 '18 at 20:09

2 Answers2

4

As @JuanR mentioned, the issue rises where too many requests are sent to the API faster than they could be handled.

To overcome this issue and have more control over sending requests, I can think of two approaches:

Using an async Method

You can define GetItemsByID as async and wait for the response to each request before proceeding to send the next one.

private static async Task GetItemsAsync(List<dynamic> list)
{
    var client = new HttpClient();

    foreach (var n in list)
    {
        var res = await client.GetAsync("http://api/users/" + n.ID);

        // Do whatever you want with `res` here
    }
}

Calling the async method

  • Call it inside an async method like this:
await GetItemsAsync(List);
  • Call it inside a non-async method:
GetItemsAsync(List).Wait();

More Control using ActionBlock

You can use ActionBlock so that multiple calls to the API at the same time "could" make the whole process faster. It is possible to limit the number of parallel calls using MaxDegreeOfParallelism:

private static void GetItemsByID(List<dynamic> list)
{
    var client = new HttpClient();
    var workerBlock = new ActionBlock<string>(async id =>
        {
            var res = await client.GetAsync("http://api/users/" + id);

            // Do whatever you want with `res` here
        },
        new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 }
    );

    foreach (var n in list)
    {
        workerBlock.Post(n.ID);
    }
    workerBlock.Complete();

    // Wait for all messages to propagate through the network.
    workerBlock.Completion.Wait();
}

Calling the method

  • Call it simply like any other method:
GetItemsById(List);

Read More

Babak
  • 1,274
  • 15
  • 18
  • The `Parallel.ForEach` [is not async-friendly](https://stackoverflow.com/questions/15136542/parallel-foreach-with-asynchronous-lambda). The lambda passed is [async void](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void). This is a very common mistake. Be careful when you pass an async delegate to a lambda. If the parameter is a `Func` it's OK. If the parameter is a simple `Action`, the method is not designed with async in mind. – Theodor Zoulias Jun 05 '20 at 07:03
  • 1
    Thanks @theodorZoulias for bringing that to my attention. Missed that part completely! – Babak Jun 05 '20 at 08:08
  • Great use case for the TPL Dataflow library! I would probably prefer to make the `GetItemsByID` method asynchronous (return `Task` instead of void), and return the `workerBlock.Completion` to the caller so that it can be awaited asynchronously. – Theodor Zoulias Jun 05 '20 at 09:07
2

Your issue is at the loop level. The loop is executing too fast, sending a lot of calls to the API, which causes the server to complain about too many requests.

You need to throttle it by slowing down the calls:

foreach(var n in List) 
{ 
    client.BaseUrl(new Uri("http://api/users/" + n.ID));

    //Add this to make the thread sleep for a second.
    System.Threading.Thread.Sleep(1000);
}

Change the milliseconds to whatever works for you.

JuanR
  • 7,405
  • 1
  • 19
  • 30
  • At this point, I don't think I have any other choice but to use delay or sleep, in any which way I see it, it looks like I will have to repeat the calls to API no matter what. It's not an Async Await issue it's the calls that I make repeatedly. Thank y'all. – PinoyDev Jan 26 '18 at 20:24
  • @PinoyDev: Don't forget to mark the answer as accepted if it helped you solve your issue. :-) – JuanR Jan 26 '18 at 20:38
  • unfortunately, I'm still hitting the limit thank you for your help – PinoyDev Jan 26 '18 at 21:05
  • @PinoyDev: Slow it down even more. If not, there may be other types of restrictions on the API. – JuanR Jan 26 '18 at 21:39
  • @JuanR I got your problem please specify the limitation on API call or share the link so that I can provide you with the optimized solution – RICKY KUMAR Jan 26 '18 at 21:47
  • Hi Ricky, its 10 request per second I believe. – PinoyDev Jan 26 '18 at 21:53