问题
GC.Collect
appears to start the garbage collection in a background thread, and then return immediately. How can I run GC.Collect
synchronously -- i.e., wait for the garbage collection to complete?
This is in the context of NUnit tests. I tried adding the gcConcurrent setting to my test assembly's app.config file, and I tried the same with nunit.exe.config. Neither had any effect -- when I debug, I can still see the finalizer being run on the "GC Finalizer Thread", rather than the thread that called GC.Collect
(NUnit's "TestRunnerThread"), and both threads are running concurrently.
Background: I want my tests to fail if they leak (don't call Dispose on) a particular class. So I've added a finalizer to that class that sets a static wasLeaked
flag; then my test TearDown calls GC.Collect()
and then throws if wasLeaked
is true. But it's not failing deterministically, because when it reads wasLeaked
, the finalizer usually hasn't even been called yet. (It fails some later test instead, after the garbage collection finally finishes.)
回答1:
Finalizers are run on a dedicated, high-priority background thread. From the background in your post, I gather that you can simply do
GC.Collect();
GC.WaitForPendingFinalizers();
The Collect()
will schedule any non-rooted instances for finalization and then the thread will wait for the finalizer thread to complete.
回答2:
You can use GC.RegisterForFullGCNotification
, trigger a full collection with GC.Collect(GC.MaxGeneration)
and then the GC.WaitForFullGCComplete
and GC.WaitForPendingFinalizers
methods, but make sure to use this in your tests only, they should not be used for production code.
回答3:
A easier/better way of doing this may be to use mocking and check an expectation that Dispose was called explicitly.
Example using 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();
}
If the method needs to create and then dispose of the object, then I would use and inject a factory class that is able to create the mock object. Example below uses stub on factory as it's not what we are testing for in this test.
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:
Finalizers always run on a separate thread regardless of whether you're using a concurrent GC or not. If you want to ensure that finalizers have been run, try GC.WaitForPendingFinalizers
instead.
来源:https://stackoverflow.com/questions/748777/run-gc-collect-synchronously