0

I have a lot of requests to send to a server (Amazon Product Advertising API), however this server doesn't allow me to send more than 1 request each second. I am looking for a free solution, based on C# abilities.

I tried to code it like this:

public static WebResponse Request(Dictionary<string, string> parameters) {
  var response = LaunchRequest(parameters);
  Sleep(1000);
  return response;
}

The problem is that multiple threads are entering in the method "Request" at the same time. If there are 4 threads, then there will be 4 requests per second.

How can my threads wait each other ?

EDIT: I tried with a lock.

So I wrote this code :

public class RequestHandler {
  private static readonly RequestHandler instance = new RequestHandler();

  private Object thisLock = new Object();

  private RequestHandler() { }

  public static RequestHandler Instance {
    get {
      return instance;
    }
  }

  public WebResponse Request(Dictionary<string, string> parameters) {
    lock (thisLock) {
      Log();
      var response = LaunchRequest(parameters);
      Sleep(1000);
      return response;
    }
  }
}

So I call my method like this :

// ...
RequestHandler requestHandler = RequestHandler.Instance;
WebResponse response = requestHandler.Request(requestHelper, parameters);
// ...

It seems to work most of the time, but sometimes, my "LaunchRequest" method is fired almost at the same time.

EDIT 2: Here are the log results, the arrows are showing the calls where there is less than 1 second:

Request: 09:52:50.230  - Thread 22
Request: 09:52:48.830  - Thread 5
Request: 09:52:47.468  - Thread 10 <---
Request: 09:52:47.331  - Thread 13 <---
Request: 09:52:45.971  - Thread 12
Request: 09:52:44.767  - Thread 11
Request: 09:52:43.230  - Thread 5
Request: 09:52:30.546  - Thread 21 <--- 
Request: 09:52:30.357  - Thread 20 <---
Request: 09:52:29.232  - Thread 13
Request: 09:52:27.908  - Thread 11
Request: 09:52:26.471  - Thread 5
Request: 09:52:25.138  - Thread 11
Request: 09:52:23.835  - Thread 12
Lawi
  • 11
  • 1

2 Answers2

2

Create an SQS queue. In your controller, put the data to the queue. Create a .net service which would poll the queue and process messages one-by-one, ensuring that only one message is sent at any given time.

UPD Not sure why you'd tag the post as AWS if you don't like AWS services, but still.

Instead of SQS you can use any of the varying queues available, e.g. MSMQ, RabbitMQ etc. The cost would be in the need to install and maintain those yourself.

Absolute worst case scenario, use a ConcurrentQueue type from C#, and have a background thread running that will pop from that queue. That will ensure that only one message is processed at a time. However, this solution is not resilient as the app pool recycling would wipe your data.

zaitsman
  • 8,984
  • 6
  • 47
  • 79
  • I am looking for a free solution. I really thought this kind of thing was doable in C#. – Lawi Feb 14 '17 at 09:59
  • 2
    @Lawi This is the correct answer. You didn't mention anywhere in your question the requirement that the solution has to be free. Further you tagged the question with AWS, and this is how you solve this with AWS. Have you looked at SQS to verify that your usage would go over the free tier? – Mark B Feb 14 '17 at 13:44
  • Well okay I had to precise that I wanted a free solution. I edited my post to add this specification. – Lawi Feb 14 '17 at 13:58
  • @Lawi when you say 'free' what do you mean? of course it's doable in c#... – zaitsman Feb 15 '17 at 08:21
  • @zaitsman I don't want any money cost. I tried the "lock" but this isn't working... – Lawi Feb 15 '17 at 08:40
  • @Lawi ready my update, all of that is 'free' in the $$ sense. You will have to invest time :) – zaitsman Feb 15 '17 at 09:00
2

Store the last call details in session or a local variable. Then you can call sleep if there is a call.

public static WebResponse Request(Dictionary<string, string> parameters) 
{
    lock (thisLock)
    {
        string lastCalled = Session["LastCalledTime"] as string;
        if (!string.IsNullOrEmpty(lastCalled) && DateTime.Parse(lastCalled) >= DateTime.Now.AddSeconds(-1))
        {
            Sleep(1000);
        }
        var response = LaunchRequest(parameters);
        Session["LastCalledTime"] = DateTime.Now.ToString("O");
    }

    return response;
 }

Added lock to make it threadsafe as highlighted by trailmax.

Community
  • 1
  • 1
Vijay
  • 523
  • 4
  • 13
  • @trailmax: I think it's fixed. – Stefan Feb 14 '17 at 11:58
  • 1
    So why exclude `LaunchRequest` from the lock? also what is the point of using session when we know that in any case we need to wait a second after a request - just do `LaunchRequest(); Thread.Sleep(1000);` – trailmax Feb 14 '17 at 12:06
  • I edited my post with a new implementation, using lock, but there is still a problem. – Lawi Feb 14 '17 at 13:18
  • @trailmax LaunchRequest does not need lock. It is a call to external resource and can consume more time. – Vijay Feb 14 '17 at 13:20
  • 1
    LaunchRequest must be in lock too, because without it you'll have race condition. – arbiter Feb 14 '17 at 14:11
  • I added logs in my implementations, you will see that sometimes there is less than 1 second between each call. – Lawi Feb 15 '17 at 08:53