Search code examples
c#apiconcurrencyqueuesemaphore

Make the POST HTTP route serve only 1 (one) request at a time (C# Web Api)


I'm trying to create a POC to learn how to create a route that runs a kind of queue for requests.

My idea is each request run once at a time

I created the following Controller:

public class ValuesController : ApiController
{
private static int _queuePosition = 0;
private SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);


public async void Post()
{
  var sendDate = DateTime.Now;
  _queuePosition++;
  await semaphore.WaitAsync();
  try
  {
    //ProcessStart
    var startProcess = DateTime.Now;
    System.Threading.Thread.Sleep(10000); //10 seg
    Email.SendMail($"Queue entry date: {sendDate} | Process Start Date: {startProcess } | Submission date: {DateTime.Now} | Queued requests: {_queuePosition}");
    semaphore.Release();
  }
  finally
  {
    _queuePosition--;
  }
}

When I send two requests with a difference of 2 sec I get the following Log emails:

  • Queue entry date: 02/01/2023 16:19:55 | Process Start Date: 02/01/2023 16:19:55 | Submission date: 02/01/2023 16:20:05 | Queued requests: 2

  • Queue entry date: 02/01/2023 16:19:57 | Process Start Date: 02/01/2023 16:19:57 | Submission date: 01/02/2023 16:20:07 | Queued requests: 2

format date: MM/dd/yyyy HH:mm:ss

Wasn't it right that the Initiation of the Process of the 2nd request started only after the 1st request ended?


Solution

  • Actually, the second request started immediately !

    I think the issue here is that you do not have One single semaphore but two instances of the semaphore. Don't know about the Framework version you are using but for example in ASP.Net Core, the controller will be instantiated for each single http request.

    I'm quite confident that you can verify this with the following change in your code, using an explicit constructor and you will see that you get two instances of the semaphore :

    public class ValuesController : ApiController
       {
           private static int _queuePosition = 0;
           private SemaphoreSlim semaphore;
    
           public ValuesController()
           {
               Console.WriteLine("This is a new semaphore");
               semaphore = new SemaphoreSlim(1, 1);
           }
    
           public async void Post()
           {
               var sendDate = DateTime.Now;
               _queuePosition++;
               await semaphore.WaitAsync();
               try
               {
                   //ProcessStart
                   var startProcess = DateTime.Now;
                   System.Threading.Thread.Sleep(10000); //10 seg
                   Email.SendMail($"Queue entry date: {sendDate} | Process Start Date: {startProcess } | Submission date: {DateTime.Now} | Queued requests: {_queuePosition}");
                   semaphore.Release();
               }
               finally
               {
                   _queuePosition--;
               }
           }
       }
    

    I'm currently having the same problem and were seeking some help. The semaphore instance should be available for the whole web API lifecycle and not only living within the context of one http request; then probably, the semaphore has to live into a singleton service and not in the controller that will use it

    I would tell you when done but it might not be immediate as this is not a priority in my roadmap.