围绕这个进行了很多讨论,每个人都倾向于同意你应该总是调用Delegate.EndInvoke来防止内存泄漏(即使是Jon Skeet说的!)。
我总是毫不疑问地遵循本指南,但最近我实现了自己的AsyncResult类,并发现唯一可能泄漏的资源是AsyncWaitHandle。
(实际上它并没有真正泄漏,因为WaitHandle使用的本机资源封装在一个具有Finalizer的SafeHandle中,但它会在垃圾收集器的finalize队列上增加压力。即便如此,一个很好的实现AsyncResult只会根据需要初始化AsyncWaitHandle ...)
了解是否存在泄漏的最佳方法就是尝试:
Action a = delegate { };
while (true)
a.BeginInvoke(null, null);
我运行了一段时间,内存保持在9-20 MB之间。
让我们与调用Delegate.EndInvoke的时间进行比较:
Action a = delegate { };
while (true)
a.BeginInvoke(ar => a.EndInvoke(ar), null);
通过这次测试,内存在9-30 MG之间播放,怪异的呃? (可能是因为当存在AsyncCallback时执行需要更长的时间,因此ThreadPool中将有更多排队的委托)
你怎么看......“神话破灭了”?
P.S。 ThreadPool.QueueUserWorkItem比Delegate.BeginInvoke效率高一百,它更适合用于fire&忘记电话。
答案 0 :(得分:11)
当前是否泄漏内存不是你应该依赖的东西。未来,框架团队可能会以一种可能导致泄密的方式改变事物,而且由于官方政策是“你必须调用EndInvoke”,那么它就是“按设计”。
你是否真的希望你的应用程序在未来的某个时间突然开始泄漏内存,因为你选择依赖于观察到的行为而不是文档化的需求?
答案 1 :(得分:6)
我确实运行了一个小测试来调用Action委托并在其中抛出异常。然后我确保通过仅维护一次运行的指定数量的线程并且在删除调用结束时连续填充线程池来确保我不会泛洪线程池。这是代码:
static void Main(string[] args)
{
const int width = 2;
int currentWidth = 0;
int totalCalls = 0;
Action acc = () =>
{
try
{
Interlocked.Increment(ref totalCalls);
Interlocked.Increment(ref currentWidth);
throw new InvalidCastException("test Exception");
}
finally
{
Interlocked.Decrement(ref currentWidth);
}
};
while (true)
{
if (currentWidth < width)
{
for(int i=0;i<width;i++)
acc.BeginInvoke(null, null);
}
if (totalCalls % 1000 == 0)
Console.WriteLine("called {0:N}", totalCalls);
}
}
让它运行大约20分钟后,超过3000万个BeginInvoke调用以后私有字节内存消耗是恒定的(23 MB)以及句柄计数。似乎没有泄漏存在。我已经通过CLR阅读了Jeffry Richters的C#书,其中他说存在内存泄漏。至少在.NET 3.5 SP1中似乎不再如此。
测试环境: Windows 7 x86 .NET 3.5 SP1 英特尔6600双核2.4 GHz
此致, Alois Kraus
答案 2 :(得分:2)
在某些情况下,BeginInvoke不需要EndInvoke(特别是在WinForms窗口消息传递中)。但是,肯定存在这种情况 - 例如BeginRead和EndRead用于异步通信。如果你想做一个即发即忘的BeginWrite,你可能会在一段时间后陷入严重的内存麻烦。
所以,你的一个测试不能定论。您需要处理许多不同类型的异步事件委托才能正确处理您的问题。
答案 3 :(得分:1)
请考虑以下示例,该示例在我的计算机上运行了几分钟,并在我决定杀死它之前达到了3.5 GB的工作集。
Action a = delegate { throw new InvalidOperationException(); };
while (true)
a.BeginInvoke(null, null);
注意:确保在没有附加调试器的情况下运行它,或者禁用“抛出异常中断”和“中断用户未处理的异常”。
编辑:正如杰夫所指出的那样,这里的内存问题不是泄漏,而只是通过排队工作比处理它更快的方式来压倒系统。实际上,通过用任何适当长的操作替换投掷可以观察到相同的行为。如果我们在BeginInvoke调用之间留出足够的时间,那么内存使用是有限的。
从技术上讲,这使得原始问题无法回答。但是,无论它是否会导致泄漏,不调用Delegate.EndInvoke是一个坏主意,因为它可能导致异常被忽略。