12 авг. 2012 г., 10:22 от abatishchevgroverboy

Использование предложения не вызывает Dispose?

Я использую 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");
    }
}

Этот код основан настатья CodeProject, Я добавил звонокmbo.Dispose() из-за утечки памяти: около 32 КБ каждый раз, когда вызывается 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 при запросе журналов событий.

Ответы на вопрос (0)

27 июл. 2016 г., 00:34 от Sepehr

Эта проблема также приводит к сбою MS Unit Test Framework и зависанию навсегда в конце выполнения всех тестов (в Visual Studio 2015, обновление 3). К сожалению, ошибка все еще сохраняется, поскольку я пишу это. В моем случае следующий код протекает:

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

И что Test Framework жалуется на то, что поток не завершается:

System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. This can happen if the test(s) started a thread but did not stop it. Make sure that all the threads started by the test(s) are stopped before completion.

И мне удалось обойти это, выполнив код в другом потоке (следовательно, после выхода из начального потока,hopefully все остальные потоки, созданные в нем, закрываются и ресурсы освобождаются соответствующим образом):

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

Я не защищаю, что это решение проблемы (на самом деле, порождение потока только для этого вызова - ужасная идея), но по крайней мере я могу снова запускать тесты без необходимости перезапускать Visual Studio каждый раз, когда он зависает ,

12 авг. 2012 г., 11:09 от Michael Graczyk

На первый взгляд, кажется, что ошибка в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 Распоряжение никогда не вызывается.

EDIT

Обычно я бы не предположил, что что-то вроде этого ошибка, но с помощьюnew заDispose обычно плохая практика (особенно сManagementBaseObject не запечатан), а так как естьno comment explaining the use of newЯ думаю, что это ошибка.

Я не смог найти запись Microsoft Connect для этой проблемы,поэтому я сделал один, Не стесняйтесь, если вы можете воспроизвести или это повлияло на вас.

14 дек. 2018 г., 13:00 от user2349783

Мы видим похожую проблему,

призваниеGC.WaitForPendingFinalizers() once is enough to fix the leak

хотя я знаю, что это не решение, а просто обходной путь

ВАШ ОТВЕТ НА ВОПРОС