我一直注意到对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一起使用。
请继续关注话题,不要引用Task Parallel Library或类似的内容;)在这里,在GC压力很重要的情况下,我们谈论的是较高性能的情况。
最佳答案
WaitMultiple
必须能够依靠未被垃圾回收的WaitHandle
。如果发生这种情况,则可能由于内存损坏或某些类似的错误而导致访问冲突。
这个想法是,您应该能够调用WaitMultiple
并销毁一个或多个WaitHandle
对象,而WaitAny
不会失败。如果不创建副本,这将是不可能的,调试该特定方案将花费您一整天的时间。因此,最重要的是,这样做是为了确保线程安全。
如果查看底层 native 函数WaitForMultipleObjects的文档,就会发现这一点:行为描述为未定义:
如下面的建议,如果要压缩所有可能的性能,那么可以确保未处置WaitHandles,并进行p/invoke调用WaitForMultipleObjects。您可以提供WaitHandle.SafeWaitHandle
作为相关同步对象的句柄。
编辑:上面给出的答案是错误的。我不时回到这个问题,因为它困扰着我。我现在相信我有一个正确的答案。
元素传输的目的是对单个WaitHandle
进行线程安全验证。如果开发人员要使用原始数组,则可能用null
值覆盖其元素之一,这将导致底层 native 函数发生未定义的行为。通过将元素复制到内部数组中,我们可以检查每个元素,如果它的null
或否则无效,则引发一个异常,然后将其存储。我们知道内部数组的元素无法替换。因此,出于长远的目的,如果您没有做诸如将null或跨AppDomain元素放入WaitHandle数组之类的怪异事情,那很好。