使用子句无法调用Dispose?

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方法,因此newDispose永远不会被调用.

编辑

通常我不会认为像这样的错误,但由于使用newDispose通常是不好的做法(特别是由于ManagementBaseObject没有密封),由于存在没有注释说明使用new,我认为这是一个错误.

我找不到这个问题的Microsoft Connect条目,所以我做了一个.如果您可以复制或者这对您有影响,请随意提升.