Запуск GC.Collect синхронно

GC.Collect появляется, чтобы запустить сборку мусора в фоновом потоке, а затем немедленно вернуться. Как я могу запустить GC.Collect синхронно - т.е. Дождаться завершения сборки мусора?

Это в контексте тестов NUnit. Я попытался добавить параметр gcConcurrent в свой файл app.config для тестовой сборки, и я попробовал то же самое с nunit.exe.config. Не было никакого эффекта - когда я отлаживаю, я все еще вижу, что финализатор запускается в "потоке Finalizer GC", а не в потоке, который называется GC.Collect (NUnit "TestRunnerThread" ), и оба потока работают одновременно.

Справочная информация. Я хочу, чтобы мои тесты терпели неудачу, если они протекают (не вызывают Dispose on) определенного класса. Поэтому я добавил финализатор к этому классу, который устанавливает статический флаг wasLeaked; то мой тест TearDown вызывает GC.Collect(), а затем бросает, если wasLeaked - true. Но это не детерминистично, потому что, когда он читает wasLeaked, финализатор обычно даже не вызывается. (Вместо этого он не выполняет более поздний тест, после завершения сборки мусора).

Ответы

Ответ 1

Финализаторы запускаются по выделенному высокоприоритетному фоновому потоку. С фона в вашем посте, я понимаю, что вы можете просто сделать

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

Collect() будет планировать любые некорневые экземпляры для финализации, а затем поток будет ждать завершения потока финализатора.

Ответ 2

Вы можете использовать GC.RegisterForFullGCNotification, запустить полную коллекцию с помощью GC.Collect(GC.MaxGeneration), а затем методы GC.WaitForFullGCComplete и GC.WaitForPendingFinalizers, но обязательно используйте это только в своих тестах, они не должны использоваться для производственного кода.

Ответ 3

Другим способом сделать это может быть использование насмешек и проверка ожидания, что Dispose вызывается явно.

Пример использования 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();
}

Если метод должен создать и затем удалить объект, я бы использовал и ввел класс factory, который может создать макет. Пример ниже использует заглушку на factory, поскольку это не то, что мы тестируем в этом тесте.

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();
}

Ответ 4

Финализаторы всегда запускаются в отдельном потоке независимо от того, используете ли вы параллельный GC или нет. Если вы хотите убедиться, что финализаторы запущены, попробуйте GC.WaitForPendingFinalizers.