同步运行GC.Collect

15

GC.Collect似乎是在后台线程中启动垃圾回收,然后立即返回。如何同步运行GC.Collect--即等待垃圾回收完成?

这是在NUnit测试的上下文中。我尝试将gcConcurrent设置添加到我的测试程序集的app.config文件中,并尝试在nunit.exe.config文件中执行相同的操作。但两者都没有产生任何效果--调试时,我仍然可以看到“GC Finalizer Thread”上运行终结器,而不是调用GC.Collect(NUnit的“TestRunnerThread”)的线程,并且这两个线程正在并发运行。

背景:我希望如果测试泄漏(未调用Dispose),它们将失败。因此,我已经向该类添加了一个终结器,该终结器设置静态wasLeaked标志; 然后,我的测试TearDown调用GC.Collect(),然后如果wasLeaked为true,则抛出异常。但是,它并不是确定性地失败,因为当读取wasLeaked时,终结器通常还没有被调用。(直到垃圾回收最终完成后,它才会在某个后续测试中失败。)

4个回答

15
终结器在专用的高优先级后台线程上运行。根据您所发布帖子中的背景,我认为您可以简单地执行以下操作:
GC.Collect();
GC.WaitForPendingFinalizers();
Collect()方法将会安排所有非根实例进行终结,然后线程将等待终结线程完成。

5
你可以使用 GC.RegisterForFullGCNotification 方法,在接下来使用 GC.Collect(GC.MaxGeneration) 触发一次完整的垃圾回收,然后使用 GC.WaitForFullGCCompleteGC.WaitForPendingFinalizers 方法等待垃圾回收完成和挂起的终结器被执行。但请确保只在测试中使用此方法,不要用于生产代码。

根据文档,WaitForFullGCApproach和WaitForFullGCComplete应该总是一起使用。当我明确地触发垃圾回收时,我如何等待垃圾回收的触发?你有一个能实现这个功能的代码示例吗? - Joe White
抱歉回复晚了。这里有一个很好的解释和示例,应该基本适用于你的代码: http://msdn.microsoft.com/en-us/library/cc713687.aspx 您可能希望选择您的通知限制,以便您基本上立即收到通知。 - Lucero

2
一个更简单/更好的方法是使用模拟并检查显式调用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();
}

如果该方法需要创建并且释放对象,则我会使用并注入一个工厂类来创建模拟对象。下面的示例在工厂上使用存根,因为这不是我们在本测试中要测试的内容。
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();
}

这是一种很好的测试确定性发布的方法,但它并不测试终结器代码是否正确工作。因此,Joe可能想根据他想要测试的内容使用两种方法。 - Lucero
我理解这是为了确保他的所有类都调用Dispose方法(3段)。 - tvanfosson
Dispose模式使用一个受保护的(虚拟)Dispose(bool disposing)方法,并由IDisposable.Dispose调用,disposing=true,由终结器(如果需要)调用disposing=false。因此,我不确定Dispose的代码路径是什么意思。 - Lucero
如果目的是测试Disposed是否被调用,这可能是一个不错的方法。但是,如果您想测试对象是否已经变得适合进行垃圾回收,仅知道它们已被处理是不够的。完全有可能保留对已处理对象的引用而没有立即的不良影响(如果没有使用它们),但这样做意味着存在内存泄漏问题,这可能是服务器中的严重问题。虽然我认为内存分析器是我们防止这种情况的最佳工具,但我赞扬任何努力寻找使其可测试的方法。 :) - The Dag

2

无论您是否使用并发GC,终结器始终在单独的线程上运行。如果要确保已运行终结器,请尝试使用GC.WaitForPendingFinalizers


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接