Para detectar posibles fugas de memoria en lugares donde ya sucedió mucho, he trabajado con pruebas que se construyen como la que se muestra a continuación. La idea principal es tener una instancia, no volver a hacer referencia a ella y dejar que el recolector de basura la recopile. No me gustaría centrarme en si esta es una buena técnica o no (en mi caso particular hizo un excelente trabajo) pero me gustaría centrarme en la siguiente pregunta:
El siguiente código funciona perfectamente en .NetFramework 4.8 pero no en .Net 5. ¿Por qué?
[Test] public void ConceptualMemoryLeakTest() { WeakReference weakReference; { object myObject = new object(); weakReference = new WeakReference(myObject); myObject = null; Assert.Null(myObject); } Assert.True(weakReference.IsAlive); // instance not collected by GC GC.Collect(); GC.WaitForPendingFinalizers(); GC.WaitForFullGCComplete(); GC.Collect(); Assert.False(weakReference.IsAlive); // instance collected by GC } Puede ver que la idea principal es trabajar con una WeakReference y usar IsAlive para determinar si el GC eliminó la instancia. ¿Cómo cambiaron las reglas en el nuevo CLR (el que se originó en dotnet core)? Sé que lo que se hace aquí no depende de algo que se especifica. Más bien, aprovecho el comportamiento que veo con CLR en NetFramework 4.8.
¿Tiene alguna idea de cómo volver a obtener algo similar que funcione también con .Net 5?
Es probable que la razón sea la compilación por niveles . En palabras simples, la compilación por niveles (para algunos métodos bajo ciertas condiciones) primero compilará una versión cruda y poco optimizada de un método, y luego preparará una mejor versión optimizada si es necesario. Esto está habilitado de manera predeterminada en .NET 5 (y .NET Core 3+), pero no está disponible en .NET 4.8.
En su caso, el resultado es que su método se compila con la compilación "rápida" mencionada y no está lo suficientemente optimizado para que su código funcione como espera (es decir, la vida útil de la variable myObject se extiende hasta el final del método). Ese es el caso incluso si compila en modo de lanzamiento con optimizaciones habilitadas y sin ningún depurador adjunto.
Puede deshabilitar la compilación por niveles agregando:
<TieredCompilation>false</TieredCompilation> Para algún <PropertyGroup> dentro del archivo csproj para su proyecto .NET 5, observará el mismo comportamiento que tiene en el caso de .NET 4.8.
Otra alternativa (además de mover la variable a otro método y devolver WeakReference desde él) es usar:
[MethodImpl(MethodImplOptions.AggressiveOptimization)] atributo en el método ConceptualMemoryLeakTest .
En realidad, con las sugerencias proporcionadas en los comentarios y las respuestas, me di cuenta de que mover la instancia a un método separado y evitar que funcione:
[MethodImpl(MethodImplOptions.NoInlining)] private WeakReference CreateWeakReference() { object myObject = new object(); return new WeakReference(myObject); } [Test] public void ConceptualMemoryLeakTest() { WeakReference weakReference = CreateWeakReference(); Assert.True(weakReference.IsAlive); GC.Collect(); GC.WaitForPendingFinalizers(); GC.WaitForFullGCComplete(); GC.Collect(); Assert.False(weakReference.IsAlive); }esto no requiere
<TieredCompilation>false</TieredCompilation>