views:

1140

answers:

1

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.

+2  A: 

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.

Ravadre
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.*
Thorarin
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.
Ravadre
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.
Thorarin
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.
Ravadre
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.
paracycle
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.
Thorarin