Question

I have been noticing that the call to WaitHandle.WaitAny allocates a copy of the WaitHandle[] given to it. As can be seen in the link below or using reflector:

http://reflector.webtropy.com/default.aspx/DotNET/DotNET/8@0/untmp/whidbey/REDBITS/ndp/clr/src/BCL/System/Threading/WaitHandle@cs/3/WaitHandle@cs

The relevant code is:

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    public static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext)
    {
        if (waitHandles==null)
        {
            throw new ArgumentNullException("waitHandles");
        }
        if (MAX_WAITHANDLES < waitHandles.Length)
        {
            throw new NotSupportedException(Environment.GetResourceString("NotSupported_MaxWaitHandles"));
        }
        if (-1 > millisecondsTimeout)
        {
            throw new ArgumentOutOfRangeException("millisecondsTimeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1"));
        }
        WaitHandle[] internalWaitHandles = new WaitHandle[waitHandles.Length];
        for (int i = 0; i < waitHandles.Length; i ++)
        {
            WaitHandle waitHandle = waitHandles[i];

            if (waitHandle == null)
                throw new ArgumentNullException(Environment.GetResourceString("ArgumentNull_ArrayElement"));

            if (RemotingServices.IsTransparentProxy(waitHandle))
                throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_WaitOnTransparentProxy"));

            internalWaitHandles[i] = waitHandle;
        }
#if _DEBUG
        // make sure we do not use waitHandles any more.
        waitHandles = null;
#endif
        int ret = WaitMultiple(internalWaitHandles, millisecondsTimeout, exitContext, false /* waitany*/ );
        for (int i = 0; i < internalWaitHandles.Length; i ++)
        {
            GC.KeepAlive (internalWaitHandles[i]);
        }
        if ((WAIT_ABANDONED <= ret) && (WAIT_ABANDONED+internalWaitHandles.Length > ret))
        {
            int mutexIndex = ret -WAIT_ABANDONED;
            if(0 <= mutexIndex && mutexIndex < internalWaitHandles.Length)
            {
                throw new AbandonedMutexException(mutexIndex,internalWaitHandles[mutexIndex]);
            }
            else
            {
                throw new AbandonedMutexException();
            }
        }
        else
            return ret;

    }

Now my question is why? Can this be circumvented (i.e. write own copy of WaitHandle.WaitAny)? And perhaps why not?

This means a lot of unnecessary memory allocations in my system. Due to the low level way we use this with multiple WaitHandles.

Please stay on topic and refrain from references to Task Parallel Library or similar ;) We are talking a high perf scenario here where GC pressure matters.

Was it helpful?

Solution

The WaitMultiple needs to be able to count on the WaitHandles not being garbage collected. If that were to occur, it would potentially end up with an access violation because of corrupt memory, or some similar nastiness.

The idea is that you should be able to call WaitMultiple and destroy one or more of the WaitHandle objects without WaitAny failing. If it didn't create a copy, this would be impossible, and debugging that particular scenario would take you all day. So bottom line is that it's done for thread safety.

If you take a look at the documentation for the underlying native function, WaitForMultipleObjects, there is evidence of this: the behavior is described as undefined:

If one of these handles is closed while the wait is still pending, the function's behavior is undefined.

As suggested below, if it's important to squeeze all the performance you can out of it, you could ensure WaitHandles are not disposed, and make a p/invoke call to WaitForMultipleObjects. You can provide WaitHandle.SafeWaitHandle as the handle to the synchronization object in question.

EDIT: The answer given above is wrong. I've come back to this question from time to time because it bothered me; I now believe I have a correct answer.

The purpose of this transfer of elements is thread-safe verification of individual WaitHandles. If the developer were to use the original array, it is possible that one of its elements could be overwritten with, say, a null value, which would result in undefined behavior in the underlying native function. By copying the elements into an internal array, we can check each one, throw an exception if it's null or otherwise invalid, and then store it. We know the internal array's elements can't be replaced. So for your long-ago purpose, you are fine if you aren't doing weird things like putting null or cross-AppDomain elements into your WaitHandle array.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top