WaitHandle.WaitAny每次被调用时都会分配WaitHandle[]的副本

本文关键字:WaitHandle 分配 副本 WaitAny 调用 | 更新日期: 2023-09-27 18:03:28

我一直注意到对WaitHandle的调用。WaitAny分配给它的WaitHandle[]的副本。如下面的链接所示,或者使用reflector:

http://reflector.webtropy.com/default.aspx/DotNET/DotNET/8@0 untmp/whidbey/REDBITS/民族/clr/src/BCL/系统/线程/WaitHandle@cs/3/WaitHandle@cs

相关代码为:

    [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;
    }

现在我的问题是为什么?这是否可以规避(即编写自己的WaitHandle.WaitAny副本)?为什么不呢?

这意味着在我的系统中有很多不必要的内存分配。由于低级的方式,我们使用了多个waithandle。

请保持主题,避免引用任务并行库或类似的;)我们在这里讨论的是一个高性能的场景,其中GC压力很重要。

WaitHandle.WaitAny每次被调用时都会分配WaitHandle[]的副本

WaitMultiple需要能够指望WaitHandle不被垃圾收集。如果发生这种情况,它可能会因为内存损坏或其他类似的问题而导致访问冲突。

这个想法是,您应该能够调用WaitMultiple并销毁一个或多个WaitHandle对象,而不会导致WaitAny失败。如果它不创建副本,这将是不可能的,并且调试特定场景将花费您一整天的时间。所以底线是,这样做是为了线程安全。

如果您看一下底层本机函数WaitForMultipleObjects的文档,就会发现这样的证据:该行为被描述为未定义:

如果这些句柄中的一个在等待还未挂起时被关闭,则该函数的行为是未定义的。

如下所示,如果重要的是要从它中挤出所有性能,您可以确保不处置WaitHandles,并对WaitForMultipleObjects进行p/invoke调用。您可以提供WaitHandle.SafeWaitHandle作为同步对象的句柄。

EDIT:上面的答案是错误的。我不时地回到这个问题上,因为它困扰着我;我现在相信我有一个正确的答案。

元素转移的目的是对单个WaitHandle进行线程安全验证。如果开发人员要使用原始数组,则可能会使用null值覆盖其中的一个元素,这将导致底层本机函数中的未定义行为。通过将元素复制到内部数组中,我们可以检查每个元素,如果它是null或其他无效则抛出异常,然后存储它。我们知道内部数组的元素不能被替换。因此,对于您很久以前的目的,如果您不做奇怪的事情,如将null或跨appdomain元素放入WaitHandle数组中,您就可以了。