【问题标题】:WaitHandle.WaitAny allocates copy of WaitHandle[] everytime it is calledWaitHandle.WaitAny 每次调用时都会分配 WaitHandle[] 的副本
【发布时间】:2013-03-12 17:06:50
【问题描述】:

我注意到对 WaitHandle.WaitAny 的调用会分配给它的 WaitHandle[] 的副本。可以在下面的链接中看到或使用反射器:

http://reflector.webtropy.com/default.aspx/DotNET/DotNET/8@0/untmp/whidbey/REDBITS/ndp/clr/src/BCL/System/Threading/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 副本)吗?也许为什么不呢?

这意味着我的系统中有很多不必要的内存分配。由于我们将其与多个 WaitHandles 一起使用的低级方式。

请停留在主题上,不要引用任务并行库或类似内容;)我们在这里谈论的是 GC 压力很重要的高性能场景。

【问题讨论】:

  • 我确定这有点晚了,但我给出了一个我认为是正确的更新答案。

标签: c# multithreading performance garbage-collection micro-optimization


【解决方案1】:

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

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

如果您查看底层原生函数 WaitForMultipleObjects 的文档,就会发现这一点:行为被描述为未定义:

如果这些句柄之一在等待仍处于挂起状态时关闭,则函数的行为未定义。

如下所示,如果尽可能发挥它的所有性能很重要,您可以确保 WaitHandles 未被释放,并对 WaitForMultipleObjects 进行 p/invoke 调用。您可以提供WaitHandle.SafeWaitHandle 作为相关同步对象的句柄。

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

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

【讨论】:

  • 这暗示了一个答案:如果你知道确定句柄不会被关闭,你可以 P/Invoke 本机函数。
  • 好,现在我们有一个更好的答案供大家欣赏。谢谢!
  • 是的,我想过这样的事情,但据我所知,复制包含等待句柄的数组并不能保证 WaitHandles 在函数调用之外被处置或关闭,所以我仍然不明白数组副本。我明白 GC.KeepAlive 可能是为了避免 GC,但为什么这是必要的?它不像数组可以被收集,因为它在调用 WaitHandle.WaitAny 的线程的堆栈上被引用。
  • 请注意,我仍然认为这不能回答我的问题,因为我认为副本还没有合理的解释......
  • 抱歉,没有看到@StevenWestbrook 编辑,我认为检查数组中的WaitHandle 是一个更合理的解释。如果您在调用 WaitAny 时知道自己在做什么,则进行不必要的检查,在这种情况下,P/Invoke 解决方案可以正常工作,并且可以避免 GC 压力。我用过这个,所以可以验证它是否有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-14
相关资源
最近更新 更多