WaitHandle.WaitAny and Semaphore class
Asked Answered
H

2

6

Edit: I'd like to plead temporary insanity for even asking this question, but it made sense at the time (see edit 2 below).

For a .NET 3.5 project, I have two types of resources (R1 and R2) that I need to check the availability of. Each resource type can have (say) 10 instances at any time.

When one of either types of resources becomes available, my worker thread needs to wake up (there is a variable number of threads). In an earlier implementation, there was only one resource type, for which I used a Semaphore to check availability.

Now I need to wait on two separate Semaphores (S1 and S2) that track availability of the resources.

WaitHandle[] waitHandles = new WaitHandle[] { s1, s2 };
int signalledHandle = WaitHandle.WaitAny(waitHandles);

switch (signalledHandle)
{
    case 0:
        // Do stuff
        s1.Release();
    case 1:
        // Do stuff
        s2.Release();
}

There is one problem with this however. From the MSDN documentation on WaitAny:

If more than one object becomes signaled during the call, the return value is the array index of the signaled object with the smallest index value of all the signaled objects.

This suggests that it's possible that I decreased both my Semaphore counts by 1 after calling WaitAny. Because signalledHandle will indicate that s1 was signalled, I will start using resource R1, and release it when I'm done. However, since I do not know if S2 was signalled or not, the availability count on this resource might now be off. If this happens 10 times, my Semaphore will be permanently 'empty' and resource R2 will not be used at all anymore.

What is the best way to deal with this? Should I switch from using two semaphores to simple counters and an AutoResetEvent to signal when either counter is changed? Am I missing some more elegant approach?

Edit 1:
According to Ravadre, only one of the Semaphores will actually be altered after WaitAny. Slightly modifying his example seems to confirm this, but is there anyone that can point me to some piece of official documentation that specifies this?

Edit 2:
I was thinking about this on my way home. Only then I realized that this must be true for WaitAny to be of any use. This problem would not be restricted to Semaphores, but just about any type of synchronization object, making WaitAny practically useless.

Hessenassau answered 19/8, 2009 at 13:51 Comment(1)
I've added a valuable (although not official) source that you might want to check.Spithead
E
5

If I understand your problem correctly, I think that your solution is perfectly ok, and you are just over interpreting the msdn quote. When calling WaitHandle.WaitAny() you will get the lowest index, but you will lock on only one waitHandle (semaphore in this case), check this sample code:


Semaphore s1 = new Semaphore(1, 2);
Semaphore s2 = new Semaphore(1, 2);

WaitHandle[] handles = new WaitHandle[] { s1, s2 };

int x = WaitHandle.WaitAny(handles);

int prevS1 = s1.Release();
int prevS2 = s2.Release();

In this scenario, prevS1 will be equal to 0, because semaphore s1 "was waited on", so it's counter has been reduced to 0, whereas prevS2 will be equal to 1, because it's state hasn't changed since it's instantiation (Release() method returns the counter before releasing, so returning 1 means "it was 1, now it's 2").

Another resource that you might want to look at : http://www.albahari.com/threading/part2.aspx#_Wait_Handles. Although it's not an "official " source, I think there's no reason to find it not reliable.

Eclat answered 19/8, 2009 at 14:22 Comment(6)
In this example, you are releasing both Semaphores unconditionally. Another quote from MSDN: "It is the programmer's responsibility to ensure that threads do not release the semaphore too many times.". If you execute this WaitAny/Release sequence twice, s2.Release() will raise an exception: Adding the given count to the semaphore would cause it to exceed its maximum count.Hessenassau
Yup, but it's a sample, that's why I've instantiated semaphores to have max counter == 2, so I know that I can release them once, without a fear of exception, what I'm proving here is, that even though both semaphores are released (counter > 0) and WaitAny() returns 0 (from your quote - smallest index) only 1st semaphore gets locked, while the other one is left without change.Spithead
The same modification also seems to support your claim that only one Semaphore will be altered (I personally find locked a little confusing in semaphore context), but I would like to see some official specs that state this is guaranteed.Hessenassau
Locked doesn't suit me as well, although I couldn't find better replacement (altered has this cons that it doesn't say if it was released or "locked"). As for guarantee - MSDN claims, that WaitAny() returns "The array index of the object that satisfied the wait.", which theoretically indicates that we are talking about one object, and only one. There's no place in the reference where they say anything about changing state of more than one object.Spithead
I think Ravadre is right. From what I understand what is trying to convey is the fact that after WaitAny returns, only the Semaphore, the index of which is returned, is acquired. Thus, even if the other Semaphore was released at the same time, it would not be acquired, thus does not need to be released. Of course, I am not sure of this and I have no documentation that I can point you to. However, it seems like it should work like this.Upstroke
I agree, it should work like this, otherwise WaitAny would be pretty much useless. At the time I found the way it was worded in MSDN somewhat confusing though.Hessenassau
O
0

For your purpose, when calling WaitHandle.WaitAny() method the result doesn't matter. What matters is one WaitHandle was signaled, so you need to try to acquire lock/synchronization again.

void Main() {
 var semaphoreOne = new SemaphoreSlim(0, 1);
 var semaphoreTwo = new SemaphoreSlim(0, 1);

 ReleaseSemaphoreAfterWhile(semaphoreOne);

 bool firstAccepted;
 bool secondAccepted = false;
 while ((firstAccepted = semaphoreOne.Wait(0)) == false &&
  (secondAccepted = semaphoreTwo.Wait(0)) == false) {
  var waitHandles = new [] {
   semaphoreOne.AvailableWaitHandle, semaphoreTwo.AvailableWaitHandle
  };
  WaitHandle.WaitAny(waitHandles);
  Console.WriteLine("SemaphoreOne Before Lock = " + semaphoreOne.CurrentCount);
  Console.WriteLine("SemaphoreTwo Before Lock = " + semaphoreTwo.CurrentCount);
 }

 if (firstAccepted) {
  Console.WriteLine("semaphore 1 was locked");
 } else if (secondAccepted) {
  Console.WriteLine("semaphore 2 was locked");
 } else {
  throw new InvalidOperationException("no semaphores were signaled");
 }
}

Random rd = new Random();
public void ReleaseSemaphoreAfterWhile(SemaphoreSlim semaphore) {
var sleepWork =(int)rd.Next(100, 1000);
 ThreadPool.QueueUserWorkItem(t => {
  Thread.Sleep(10000 + sleepWork);
  semaphore.Release();
 });
}

There are room for other implementations with the same idea/logic, but using while loop in that way you guaranteed that only one semaphore is going to get acquired, and if there's no room, it locks the thread until any of the WaitHandle gets signaled - considering SemaphoreSlim instance .Release() method.

Unfortunately (as pointed in the comments) they're some misunderstanding about thread synchronization in the web, but that code above should help you to solve your problem.

Obituary answered 25/8, 2017 at 1:48 Comment(1)
Read the documentation for AvailableWaitHandle again, waiting on it does NOT acquire the semaphore itself.Cisneros

© 2022 - 2024 — McMap. All rights reserved.