【发布时间】:2012-08-07 10:30:46
【问题描述】:
我正在使用 Visual Studio 2010 来定位 .NET 4.0 客户端配置文件。我有一个 C# 类来检测给定进程何时开始/终止。为此,该类使用一个 ManagementEventWatcher,其初始化如下; query、scope 和 watcher 是类字段:
query = new WqlEventQuery();
query.EventClassName = "__InstanceOperationEvent";
query.WithinInterval = new TimeSpan(0, 0, 1);
query.Condition = "TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'notepad.exe'";
scope = new ManagementScope(@"\\.\root\CIMV2");
watcher = new ManagementEventWatcher(scope, query);
watcher.EventArrived += WatcherEventArrived;
watcher.Start();
EventArrived 事件的处理程序如下所示:
private void WatcherEventArrived(object sender, EventArrivedEventArgs e)
{
string eventName;
var mbo = e.NewEvent;
eventName = mbo.ClassPath.ClassName;
mbo.Dispose();
if (eventName.CompareTo("__InstanceCreationEvent") == 0)
{
Console.WriteLine("Started");
}
else if (eventName.CompareTo("__InstanceDeletionEvent") == 0)
{
Console.WriteLine("Terminated");
}
}
此代码基于a CodeProject article。我添加了对mbo.Dispose() 的调用,因为它泄漏了内存:每次引发 EventArrived 时大约 32 KB,每秒一次。泄漏在 WinXP 和 Win7(64 位)上都很明显。
到目前为止一切顺利。为了尽心尽责,我添加了一个try-finally 子句,如下所示:
var mbo = e.NewEvent;
try
{
eventName = mbo.ClassPath.ClassName;
}
finally
{
mbo.Dispose();
}
那里没问题。更好的是,C# using 子句更紧凑但等效:
using (var mbo = e.NewEvent)
{
eventName = mbo.ClassPath.ClassName;
}
太好了,只是现在内存泄漏又回来了。发生了什么?
嗯,我不知道。但是我尝试用ILDASM反汇编这两个版本,它们几乎但不完全相同。
来自try-finally的IL:
.try
{
IL_0030: nop
IL_0031: ldloc.s mbo
IL_0033: callvirt instance class [System.Management]System.Management.ManagementPath [System.Management]System.Management.ManagementBaseObject::get_ClassPath()
IL_0038: callvirt instance string [System.Management]System.Management.ManagementPath::get_ClassName()
IL_003d: stloc.3
IL_003e: nop
IL_003f: leave.s IL_004f
} // end .try
finally
{
IL_0041: nop
IL_0042: ldloc.s mbo
IL_0044: callvirt instance void [System.Management]System.Management.ManagementBaseObject::Dispose()
IL_0049: nop
IL_004a: ldnull
IL_004b: stloc.s mbo
IL_004d: nop
IL_004e: endfinally
} // end handler
IL_004f: nop
来自using的IL:
.try
{
IL_002d: ldloc.2
IL_002e: callvirt instance class [System.Management]System.Management.ManagementPath [System.Management]System.Management.ManagementBaseObject::get_ClassPath()
IL_0033: callvirt instance string [System.Management]System.Management.ManagementPath::get_ClassName()
IL_0038: stloc.1
IL_0039: leave.s IL_0045
} // end .try
finally
{
IL_003b: ldloc.2
IL_003c: brfalse.s IL_0044
IL_003e: ldloc.2
IL_003f: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0044: endfinally
} // end handler
IL_0045: ldloc.1
显然问题出在这一行:
IL_003c: brfalse.s IL_0044
相当于if (mbo != null),所以永远不会调用mbo.Dispose()。但是如果可以访问.ClassPath.ClassName,mbo 怎么可能为空呢?
对此有什么想法吗?
另外,我想知道这种行为是否有助于解释此处未解决的讨论:Memory leak in WMI when querying event logs。
【问题讨论】:
-
我强烈怀疑您误诊了这一点。我从未见过
using语句失败。请注意,您的try/finally版本当前无法编译,因此这显然不是您的真实代码。您能否发布一个简短但完整的程序来演示该问题? -
@JonSkeet 你是对的,现在尝试终于修复了。
-
@groverboy 没关系,但从 IL 看来,您的
try/finally代码也将mbo设置为null,除非它只是 Debug 构建自动执行此操作。 . -
@MichaelGraczyk 不,你是对的,原来的 try-finally 包含了
mbo = null,我认为这是多余的。 -
@groverboy 我在我编辑的链接上提交了一个连接条目。