A cláusula de uso falha ao chamar Dispose?
Estou usando o Visual Studio 2010 para direcionar o .NET 4.0 Client Profile. Eu tenho uma classe c # para detectar quando um determinado processo inicia / termina. Para isso, a classe usa um ManagementEventWatcher, que é inicializado como abaixo;query
, scope
ewatcher
são campos de classe:
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();
O manipulador do evento EventArrived se parece com isto:
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");
}
}
Este código é baseado emum artigo da CodeProject. Eu adicionei a chamada parambo.Dispose()
porque vazou memória: cerca de 32 KB toda vez que o EventArrived é gerado, uma vez por segundo. O vazamento é óbvio em WinXP e Win7 (64 bits).
Por enquanto, tudo bem. Tentando ser consciencioso eu adicionei umtry-finally
cláusula, assim:
var mbo = e.NewEvent;
try
{
eventName = mbo.ClassPath.ClassName;
}
finally
{
mbo.Dispose();
}
Não há problema aí. Melhor ainda, o c #using
cláusula é mais compacta, mas equivalente:
using (var mbo = e.NewEvent)
{
eventName = mbo.ClassPath.ClassName;
}
Ótimo, só agora o vazamento de memória está de volta. O que aconteceu?
Bem, eu não sei. Mas eu tentei desmontar as duas versões com o ILDASM, que são quase, mas não exatamente o mesmo.
IL detry-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 deusing
:
.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
Aparentemente, o problema é esta linha:
IL_003c: brfalse.s IL_0044
que é equivalente aif (mbo != null)
, assimmbo.Dispose()
nunca é chamado. Mas como é possível que o mbo seja nulo se fosse capaz de acessar.ClassPath.ClassName
?
Alguma idéia sobre isso?
Além disso, estou me perguntando se esse comportamento ajuda a explicar a discussão não resolvida aqui:Vazamento de memória no WMI ao consultar logs de eventos.