c# - El uso de la cláusula no se puede llamar a Dispose?
.net wmi (2)
A primera vista, parece haber un error en ManagementBaseObject
.
Aquí está el método Dispose()
de ManagementBaseObject
:
public new void Dispose()
{
if (_wbemObject != null)
{
_wbemObject.Dispose();
_wbemObject = null;
}
base.Dispose();
GC.SuppressFinalize(this);
}
Observe que está declarado como new
. También tenga en cuenta que cuando la instrucción de using
llama a Dispose
, lo hace con la implementación de la interfaz explícita. Por lo tanto, se llama al método principal Component.Dispose()
y nunca se llama a _wbemObject.Dispose()
. ManagementBaseObject.Dispose()
no debe declararse como new
aquí. No me crees Aquí hay un comentario de Component.cs
, justo arriba de su método 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);
/// }
Como aquí la declaración de using
llama al método explícito IDisposable.Dispose
, nunca se llama al new
Dispose.
EDITAR
Normalmente, no asumo que algo como esto sea un error, pero dado que usar new
para Dispose
suele ser una mala práctica (especialmente porque ManagementBaseObject
no está sellado), y como no hay comentarios que expliquen el uso de new
, creo que es un error.
No pude encontrar una entrada de Microsoft Connect para este problema, así que hice una . Siéntase libre de votar si puede reproducirse o si esto le ha afectado.
Estoy usando Visual Studio 2010 para apuntar al perfil de cliente de .NET 4.0. Tengo una clase de C # para detectar cuándo comienza / termina un proceso dado. Para esto, la clase utiliza un ManagementEventWatcher, que se inicializa como se muestra a continuación; query
, scope
y el watcher
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 en un artículo de CodeProject . mbo.Dispose()
la llamada a mbo.Dispose()
porque mbo.Dispose()
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 concienzudo, agregué una cláusula try-finally
, como esta:
var mbo = e.NewEvent;
try
{
eventName = mbo.ClassPath.ClassName;
}
finally
{
mbo.Dispose();
}
No hay problema allí. Mejor aún, la cláusula de using
C # es más compacta pero equivalente:
using (var mbo = e.NewEvent)
{
eventName = mbo.ClassPath.ClassName;
}
Grande, sólo 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 de 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 de 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
Al parecer el problema es esta línea:
IL_003c: brfalse.s IL_0044
que es equivalente a if (mbo != null)
, por lo que mbo.Dispose()
nunca se llama. Pero, ¿cómo es posible que mbo sea nulo si pudo acceder a .ClassPath.ClassName
?
Tiene alguna idea sobre esto?
Además, me pregunto si este comportamiento ayuda a explicar la discusión no resuelta aquí: Fuga de memoria en WMI al consultar registros de eventos .
Este problema también hace que MS Unit Test Framework falle y se cuelgue para siempre al final de la ejecución de todas las pruebas (en Visual Studio 2015, actualización 3). Desafortunadamente el error aún persiste mientras escribo esto. En mi caso, el siguiente código es fugas:
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
{
....
}
Y de lo que se está quejando Test Framework es que un subproceso no se está cerrando:
System.AppDomainUnloadedException: intento acceder a un dominio de aplicación descargado. Esto puede suceder si las pruebas iniciaron un subproceso pero no lo detuvieron . Asegúrese de que todos los subprocesos iniciados por la (s) prueba (s) se hayan detenido antes de finalizar.
Y logré sortearlo ejecutando el código en otro subproceso (por lo tanto, después de que el subproceso inicial se cierra, es de esperar que todos los demás subprocesos se cierren y los recursos se liberen de manera apropiada):
Thread searcherThread = new Thread(new ThreadStart(() =>
{
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
{
....
}
}));
searcherThread.Start();
searcherThread.Join();
No estoy abogando por que esta sea la solución al problema (de hecho, generar un hilo solo para esta llamada es una idea horrible), pero al menos puedo ejecutar pruebas nuevamente sin necesidad de reiniciar Visual Studio cada vez que se cuelga. .