【问题标题】:Run GC.Collect synchronously同步运行 GC.Collect
【发布时间】:2009-04-14 18:23:53
【问题描述】:

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

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

背景:我希望我的测试在泄漏(不要调用 Dispose on)特定类时失败。所以我为那个设置了一个静态wasLeaked标志的类添加了一个终结器;然后我的测试 TearDown 调用 GC.Collect() 然后如果 wasLeaked 为真则抛出。但它并没有确定性地失败,因为当它读取wasLeaked 时,通常甚至还没有调用终结器。 (在垃圾收集最终完成后,它会失败一些稍后的测试。)

【问题讨论】:

    标签: .net nunit garbage-collection


    【解决方案1】:

    终结器在专用的高优先级后台线程上运行。从您帖子的背景来看,我认为您可以简单地做到这一点

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

    Collect() 将调度任何非根实例进行终结,然后线程将等待终结器线程完成。

    【讨论】:

    • 完美!这正是我一直在寻找的。谢谢!
    【解决方案2】:

    您可以使用GC.RegisterForFullGCNotification,使用GC.Collect(GC.MaxGeneration) 触发完整收集,然后使用GC.WaitForFullGCCompleteGC.WaitForPendingFinalizers 方法,但请确保仅在您的测试中使用它,它们不应用于生产代码。

    【讨论】:

    • 文档说 WaitForFullGCApproach 和 WaitForFullGCComplete 应该始终一起使用。当我明确触发 GC 时,我将如何等待 GC 方法?您有执行此操作的代码示例吗?
    • 抱歉,回答迟了。这里有一个很好的解释和示例,应该或多或少适用于您的代码:msdn.microsoft.com/en-us/library/cc713687.aspx 您可能需要选择您的方法通知限制,以便您基本上立即收到通知。
    【解决方案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();
    }
    

    如果方法需要创建然后处理对象,那么我将使用并注入一个能够创建模拟对象的工厂类。下面的示例在工厂中使用存根,因为这不是我们在此测试中测试的内容。

    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 可能希望同时使用这两种方法,具体取决于他想要准确测试的内容。
    • 我知道这是为了确保他的所有课程都称为 Dispose(第 3 段)。
    • Dispose 模式使用受保护的(虚拟)Dispose(bool disposing) 方法,由 IDIsposable.Dispose 调用,disposing=true,终结器(如果需要)调用 disposing=false。因此,我不确定 Dispose 的代码路径是什么意思。
    • 如果目的是测试 Disposed 是否被调用,这可能是一个好方法。但是如果你想测试对象是否有资格进行垃圾回收,那么仅仅知道它们已经被处理是没有好处的。完全有可能保留对已处置对象的引用而不会立即产生不良影响(如果没有使用它们),但这样做意味着内存泄漏,这可能是服务器中的一个严重问题。虽然我怀疑内存分析器是我们最好的解决这个问题的工具,但我赞赏任何努力寻找使其可测试的方法。 :)
    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2012-11-25
      • 1970-01-01
      • 1970-01-01
      • 2021-09-23
      • 1970-01-01
      • 1970-01-01
      • 2021-10-25
      • 2020-10-19
      • 1970-01-01
      相关资源
      最近更新 更多