gro*_*boy 24 .net c# wmi using
我正在使用Visual Studio 2010来定位.NET 4.0 Client Profile.我有一个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();
Run Code Online (Sandbox Code Playgroud)
事件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");
}
}
Run Code Online (Sandbox Code Playgroud)
此代码基于CodeProject文章.我添加了调用,mbo.Dispose()
因为它泄漏了内存:每次引发EventArrived时大约32 KB,每秒一次.WinXP和Win7(64位)上的漏洞很明显.
到现在为止还挺好.为了尽职尽责,我添加了一个try-finally
条款,如下所示:
var mbo = e.NewEvent;
try
{
eventName = mbo.ClassPath.ClassName;
}
finally
{
mbo.Dispose();
}
Run Code Online (Sandbox Code Playgroud)
没问题.更好的是,C#using
子句更紧凑但相当于:
using (var mbo = e.NewEvent)
{
eventName = mbo.ClassPath.ClassName;
}
Run Code Online (Sandbox Code Playgroud)
很好,只是现在内存泄漏又回来了.发生了什么?
好吧,我不知道.但我尝试用ILDASM拆解这两个版本,这几乎是不一样的.
IL来自try-finally
:
.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
Run Code Online (Sandbox Code Playgroud)
IL来自using
:
.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
Run Code Online (Sandbox Code Playgroud)
显然问题是这一行:
IL_003c: brfalse.s IL_0044
Run Code Online (Sandbox Code Playgroud)
这相当于if (mbo != null)
,所以mbo.Dispose()
永远不会被称为.但是,如果mbo能够访问,mbo怎么可能为null .ClassPath.ClassName
?
有什么想法吗?
另外,我想知道这种行为是否有助于解释此处未解决的讨论:查询事件日志时WMI中的内存泄漏.
Mic*_*zyk 34
乍一看,似乎有一个错误ManagementBaseObject
.
这是以下Dispose()
方法ManagementBaseObject
:
public new void Dispose()
{
if (_wbemObject != null)
{
_wbemObject.Dispose();
_wbemObject = null;
}
base.Dispose();
GC.SuppressFinalize(this);
}
Run Code Online (Sandbox Code Playgroud)
请注意,它被声明为new
.还要注意,当using
语句调用时Dispose
,它使用显式接口实现.因此Component.Dispose()
调用父方法,并且_wbemObject.Dispose()
永远不会调用它. ManagementBaseObject.Dispose()
不应该在new
这里声明.不相信我?这是一个评论Component.cs
,就在它的Dispose(bool)
方法之上:
/// <para>
/// For base classes, you should never override the Finalier (~Class in C#)
/// or the Dispose method that takes no arguments, rather you should
/// always override the Dispose method that takes a bool.
/// </para>
/// <code>
/// protected override void Dispose(bool disposing) {
/// if (disposing) {
/// if (myobject != null) {
/// myobject.Dispose();
/// myobject = null;
/// }
/// }
/// if (myhandle != IntPtr.Zero) {
/// NativeMethods.Release(myhandle);
/// myhandle = IntPtr.Zero;
/// }
/// base.Dispose(disposing);
/// }
Run Code Online (Sandbox Code Playgroud)
由于此处using
语句调用显式IDisposable.Dispose
方法,因此new
Dispose永远不会被调用.
编辑
通常我不会认为像这样的错误,但由于使用new
的Dispose
通常是不好的做法(特别是由于ManagementBaseObject
没有密封),由于存在没有注释说明使用new
,我认为这是一个错误.
我找不到这个问题的Microsoft Connect条目,所以我做了一个.如果您可以复制或者这对您有影响,请随意提升.