El uso de la cláusula no se puede llamar a Dispose?
Estoy usando Visual Studio 2010 para apuntar al perfil de cliente de .NET 4.0. Tengo una clase de C # para detectar cuándo se inicia / termina un proceso dado. Para esto, la clase utiliza un ManagementEventWatcher, que se inicializa como se muestra a continuación;query
, scope
ywatcher
son campos de clase:
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();
El controlador para el evento EventArrived se ve así:
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 se basa enun artículo de CodeProject. Agregué la llamada ambo.Dispose()
porque perdía memoria: aproximadamente 32 KB cada vez que se genera EventArrived, una vez por segundo. La fuga es obvia tanto en WinXP como en Win7 (64 bits).
Hasta ahora tan bueno. Tratando de ser concienzuda agregué untry-finally
cláusula, así:
var mbo = e.NewEvent;
try
{
eventName = mbo.ClassPath.ClassName;
}
finally
{
mbo.Dispose();
}
No hay problema allí. Mejor aún, el C #using
La cláusula es más compacta pero equivalente:
using (var mbo = e.NewEvent)
{
eventName = mbo.ClassPath.ClassName;
}
Genial, solo que ahora la pérdida de memoria está de vuelta. ¿Que pasó?
Bueno, no lo sé. Pero intenté desmontar las dos versiones con ILDASM, que son casi pero no exactamente iguales.
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
Al parecer el problema es esta línea:
IL_003c: brfalse.s IL_0044
que es equivalente aif (mbo != null)
, asi quembo.Dispose()
nunca se llama Pero, ¿cómo es posible que mbo sea nulo si pudiera acceder?.ClassPath.ClassName
?
Tiene alguna idea sobre esto?
Además, me pregunto si este comportamiento ayuda a explicar la discusión no resuelta aquí:Pérdida de memoria en WMI al consultar registros de eventos.