2009-04-14 6 views
12

GC.Collect parece iniciar la recolección de elementos no utilizados en un subproceso en segundo plano, y luego volver inmediatamente. ¿Cómo puedo ejecutar GC.Collect de forma síncrona, es decir, esperar a que se complete la recolección de elementos no utilizados?Ejecute GC.Colgar sincrónicamente

Esto es en el contexto de las pruebas NUnit. Traté de agregar la configuración gcConcurrent al archivo app.config de mi ensamblaje de prueba, e intenté lo mismo con nunit.exe.config. Ninguno tuvo ningún efecto: cuando depuro, todavía puedo ver el finalizador ejecutándose en el "subproceso GC Finalizer", en lugar del subproceso llamado GC.Collect ("TestRunnerThread" de NUnit), y ambos subprocesos se ejecutan al mismo tiempo.

Antecedentes: deseo que mis pruebas fallen si tienen fugas (no llame a Dispose on) en una clase en particular. Así que agregué un finalizador a esa clase que establece un indicador estático wasLeaked; entonces mi prueba TearDown llama al GC.Collect() y luego arroja si wasLeaked es verdadero. Pero no falla de manera determinista, porque cuando lee wasLeaked, el finalizador generalmente ni siquiera se ha llamado aún. (Falla alguna prueba más adelante en su lugar, después de la recolección de basura, finalmente termina.)

Respuesta

12

Los finalizadores se ejecutan en un subproceso de fondo dedicado de alta prioridad. Desde el fondo en tu post, supongo que simplemente puede hacer

GC.Collect(); 
GC.WaitForPendingFinalizers(); 

El Collect() programará ninguna instancia no arraigados para su formalización y luego el hilo esperará a que el subproceso finalizador para completar.

+0

¡Perfecto! Esto es exactamente lo que estaba buscando. ¡Gracias! –

5

Puede utilizar GC.RegisterForFullGCNotification, desencadenar una colección completa con GC.Collect(GC.MaxGeneration) y luego los métodos GC.WaitForFullGCComplete y GC.WaitForPendingFinalizers, pero asegúrese de usar esto en sólo sus pruebas, no deben usarse para el código de producción.

+0

Los documentos dicen que WaitForFullGCApproach y WaitForFullGCComplete siempre se deben usar juntos. ¿Cómo esperaría un enfoque GC cuando exploto explícitamente el GC? ¿Tienes una muestra de código que hace esto? –

+0

Perdón por la respuesta tardía. Aquí hay una buena explicación y muestra que debería aplicarse más o menos a su código: http://msdn.microsoft.com/en-us/library/cc713687.aspx Puede elegir su límite de notificación de aproximación para que básicamente recibes una notificación de inmediato. – Lucero

2

Una forma más fácil/mejor de hacer esto puede ser utilizar burlas y verificar la expectativa de que se invocó a Dispose explícitamente.

Ejemplo utilizando RhinoMocks

public void SomeMethodTest() 
{ 
    var disposable = MockRepository.GenerateMock<DisposableClass>(); 

    disposable.Expect(d => d.Dispose()); 

    // use constructor injection to pass in mock `DisposableClass` object 
    var classUnderTest = new ClassUnderTest(disposable); 

    classUnderTest.SomeMethod(); 

    disposable.VerifyAllExpectations(); 
} 

Si el método necesita crear y luego disponer del objeto, entonces me gustaría utilizar e inyectar una clase de fábrica que es capaz de crear el objeto simulado. El siguiente ejemplo usa un stub en la fábrica ya que no es lo que estamos probando en esta prueba.

public void SomeMethod2Test() 
{ 
    var factory = MockRepository.Stub<DisposableFactory>(); 
    var disposable = MockRepository.GenerateMock<DisposableClass>(); 

    factory.Stub(f => f.CreateDisposable()).Return(disposable);   
    disposable.Expect(d => d.Dispose()); 

    // use constructor injection to pass in mock factory 
    var classUnderTest = new ClassUnderTest(factory); 

    classUnderTest.SomeMethod(); 

    disposable.VerifyAllExpectations(); 
} 
+0

Este es un buen enfoque para probar el lanzamiento determinista, pero no prueba si el código del finalizador funciona correctamente. Por lo tanto, es posible que Joe quiera usar ambos enfoques, según lo que quiera probar exactamente. – Lucero

+0

Entendí que era para asegurarme de que todas sus clases se llamaran Dispose (3 párrafos). – tvanfosson

+0

El patrón Dispose usa un método Dispose (bool disposing) protegido (virtual) y es llamado por IDIsposable.Deseche con disposing = true y con el finalizador (si es necesario) con disposing = false. Por lo tanto, no estaba seguro de qué significado tenía el código de Disposición. – Lucero

2

finalizadores siempre se ejecutan en un hilo separado, independientemente de si usted está utilizando una basura simultánea o no. Si desea asegurarse de que se hayan ejecutado los finalizadores, intente con GC.WaitForPendingFinalizers.

Cuestiones relacionadas