Запуск 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
.