Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does SemaphoreSlim's Wait(Int32) method return immediately when passed zero?

In the MSDN docs for Semaphore's WaitOne(Int32) method, it says that giving it a value of zero will cause the method to return immediately without waiting for a slot to open up. The documentation for the SemaphoreSlim version does not say the same thing. Does SemaphoreSlim share this behavior?

I'm not sure how to guarantee the timing to test this myself.

like image 692
Ross Avatar asked Jan 29 '13 01:01

Ross


People also ask

How SemaphoreSlim works?

This defines the semaphore's count. The count is decremented each time a thread enters the semaphore, and incremented each time a thread releases the semaphore. To enter the semaphore, a thread calls one of the Wait or WaitAsync overloads. To release the semaphore, it calls one of the Release overloads.

What is initial count in semaphore?

The initial count of a semaphore is typically set to the maximum value. The count is then decremented from that level as the protected resource is consumed. Alternatively, you can create a semaphore with an initial count of zero to block access to the protected resource while the application is being initialized.


1 Answers

If passed a timeout of 0 then SemaphoreSlim.Wait will always attempt to acquire a slot before returning, it just won't wait around for one for longer than a few operations and a SpinOnce.

EDIT: clarification: This is probably the same apparent behaviour as Semaphore. From the documentation for Semaphore:

It tests the state of the wait handle and returns immediately

However SemaphoreSlim does give a slot a chance of opening up once you are in the Wait method, through the use of SpinOnce.

(end edit)

Also, SemaphoreSlim runs a few operations before attempting to acquire a slot. One of these is a Monitor.Enter, so it could wait on other threads at that point who are in the process of waiting or releasing. It will not necessarily, therefore, return immediately.

As far as I know the order of events is:

  1. Create a CancellationTokenRegistration
  2. If the available slot count is 0, then SpinWait.SpinOnce (or skip this if NextSpinWillYield is true) (edit: I made this bold to emphasise that step 2 does the same test as step 5, giving it a chance to spin and have a slot become available before the final test and exit)
  3. Call Monitor.Enter to enter the lock (the same lock that Release and WaitAsync enters)
  4. Increment the internal waitCount by 1
  5. If the available slot count is 0, decrement waitCount by 1 and Exit the lock, then return false
  6. If we get to here and there is a free slot:
    • decrement the available slot count by 1 (implication: acquire the slot)
    • Reset the waitHandle under AvailableWaitHandle, if it is being used and the available slot count is now 0 again
    • Decrement waitCount by 1
    • Exit the lock
    • Dispose of the CancellationTokenRegistration
    • return true

(Note that other threads who are using non-zero timeouts intermittently release and acquire the lock used to protect the counters by calling Monitor.Wait, so you won't wait forever with a timeout of 0, just for a very short time.)

So SemaphoreSlim would not seem to share the exact same 0 timeout behaviour with Sempahore, as it does give a slot a chance to open up. (Maybe this is why there is Semaphore.WaitOne and SemaphoreSlim.Wait - to cause code not to compile when upgrading old code by just changing the instantiation of the Semaphore, and therefore making us check the behaviour).

The article Semaphore vs. SemaphoreSlim doesn't highlight this behaviour, just the fundamental differences between the two.

Side note:

Interestingly that reference also states

[SemaphoreSlim] does not support ... the use of a wait handle for synchronization

And yet the documentation for SemaphoreSlim.AvailableWaitHandle says differently.

like image 87
Andy Brown Avatar answered Sep 20 '22 23:09

Andy Brown