使用条款未能调用Dispose?(Using clause fails to call Dispos

2019-06-26 22:25发布

我使用Visual Studio 2010的目标.NET 4.0客户端配置文件。 我有一个C#类的指定程序启动时检测/终止。 为此,类使用ManagementEventWatcher,其被初始化为以下; queryscopewatcher是类字段:

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");
    }
}

这个代码是基于一个CodeProject上的文章 。 我添加了调用mbo.Dispose()因为它泄漏的内存:约32 KB每EventArrived时间上升,每秒一次。 泄漏是在两个的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,这是几乎,但不完全一样。

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

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

显然,问题是这一行:

IL_003c:  brfalse.s  IL_0044

这相当于if (mbo != null)所以mbo.Dispose()不会被调用。 但是,如何可能MBO为空,如果它能够访问.ClassPath.ClassName

在这个有什么想法?

另外,我想知道,如果这种现象有助于解释悬而未决的讨论在这里: 查询事件日志时,内存泄漏的WMI 。

Answer 1:

乍一看,似乎是在一个错误ManagementBaseObject

这里的Dispose()的方法ManagementBaseObject

    public new void Dispose() 
    {
        if (_wbemObject != null) 
        {
            _wbemObject.Dispose();
            _wbemObject = null;
        } 
        base.Dispose();
        GC.SuppressFinalize(this); 
    } 

请注意,它被声明为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); 
    ///    }

由于这里using语句调用明确IDisposable.Dispose方法,在new处置不会被调用。

编辑

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

我无法找到针对此问题的Microsoft连接项, 所以我做了一个 。 随意给予好评,如果你能复制或如果这已经影​​响到你。



Answer 2:

这个问题也引起MS单元测试框架失败,并且在运行所有测试结束永远挂(Visual Studio的2015年,更新3下)。 不幸的是,错误仍然存​​在,因为我写的。 在我的情况下,下面的代码是泄漏:

using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
{
    ....
}

什么测试框架的抱怨是关于没有被关闭线程:

System.AppDomainUnloadedException:试图访问已卸载的AppDomain。 如果测试(S)启动一个线程,但并没有阻止它发生这种情况 。 确保通过测试(S)开始的所有线程都完成之前停止。

我设法在另一个线程中执行代码(因此,启动线程退出后, 希望在它产生的所有其他线程关闭和资源适当地释放)绕过它:

Thread searcherThread = new Thread(new ThreadStart(() =>
{
    using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
    {
        ....
    }
}));
searcherThread.Start();
searcherThread.Join();

我不主张,这是解决问题的方法(如事实上,产生线程只是这个呼叫是一个可怕的想法),但至少我可以在不需要重新启动Visual Studio每次挂起时间再次运行测试。



Answer 3:

我们也看到类似的问题,

调用GC.WaitForPendingFinalizers()一次就够修理泄漏

虽然我知道这是不是一个解决方案,只是一个解决办法的bug



文章来源: Using clause fails to call Dispose?
标签: c# .net wmi using