这种GC.SuppressFinalize()的使用感觉不对

时间:2013-03-22 00:05:04

标签: c# .net garbage-collection

我一直在使用供应商库时遇到一些问题,当库中计算的实体应该始终在其中有效数据时,该库实际偶尔会为空。

功能代码(在调试供应商的问题之后)大致如下:

    Task.Factory.StartNew(() => ValidateCalibration(pelRectRaw2Ds, crspFeatures, Calibration.Raw2DFromPhys3Ds));

    .....

    private void ValidateCalibration(List<Rectangle> pelRectRaw2Ds, List<List<3DCrspFeaturesCollection>> crspFeatures, List<3DCameraCalibration> getRaw2DFromPhys3Ds)
    {
        var calibrationValidator = new 3DCameraCalibrationValidator();

        // This is required according to vendor otherwise validationResultsUsingRecomputedExtrinsics is occasionally null after preforming the validation
        GC.SuppressFinalize(calibrationValidator);

        3DCameraCalibrationValidationResult validationResultUsingOriginalCalibrations;
        3DCameraCalibrationValidationResult validationResultsUsingRecomputedExtrinsics;
        calibrationValidator.Execute(pelRectRaw2Ds, crspFeatures, getRaw2DFromPhys3Ds, out validationResultUsingOriginalCalibrations, out validationResultsUsingRecomputedExtrinsics);

        Calibration.CalibrationValidations.Add(new CalibrationValidation
            {
                Timestamp = DateTime.Now,
                UserName = Globals.InspectionSystemObject.CurrentUserName,
                ValidationResultUsingOriginalCalibrations = validationResultUsingOriginalCalibrations,
                ValidationResultsUsingRecomputedExtrinsics = validationResultsUsingRecomputedExtrinsics
            });
    }

验证过程是一个相当耗时的操作,所以我将其交给任务。我遇到的问题是,最初我没有调用GC.SuppressFinalize(calibrationValidator),当应用程序从Release版本运行时,out参数validationResultsUsingRecomputedExtrinsics将为null。如果我从Debug构建运行应用程序(无论是否附带调试器),那么validationResultsUsingRecomputedExtrinsics将包含有效数据。

我不完全理解GC.SuppressFinalize()在这种情况下做了什么,或者它是如何解决问题的。我能找到的关于GC.SuppressFinalize()的一切都是在实现IDisposable时使用的。我在“标准”代码中找不到任何使用它。

如何/为什么添加对GC.SuppressFinalize(calibrationValidator)的调用可以解决这个问题?

据我所知,如果没有对供应商库内部的深入了解,可能无法确切知道,但任何见解都会有所帮助。

该应用程序使用针对.NET 4.0的VS2012编译。该供应商库要求在app.config中指定useLegacyV2RuntimeActivationPolicy =“true”选项。

这是我从供应商那里得到的理由:

  

SuppressFinalize命令确保垃圾收集器不会“提前”清理某些内容。似乎由于某种原因,你的应用程序有时让垃圾收集器有点热心并在你真正完成之前清理对象;它几乎肯定与范围相关,并且可能是由于多线程导致校准的雾化器范围的混淆。以下是我从Engineering获得的回复。

     

因为变量是在本地范围内创建的,并且该函数在后台线程中运行,所以垃圾收集在主线程中运行,并且似乎垃圾收集在处理多线程情况时不够智能。有时,它只是过早发布(验证器的内部执行尚未完成,仍然需要此变量)。

3 个答案:

答案 0 :(得分:16)

这很可能是解决过早垃圾收集问题的黑客攻击。非托管代码并不罕见,在相机应用程序中很常见。这不是一个健康的黑客,这很可能导致资源泄漏,因为终结器不会执行。非托管代码的包装器几乎总是在终结器中有所作为,他们需要释放非托管内存是很常见的。

问题在于,在非托管代码运行时,可以对calibrationValidator对象进行垃圾回收。在程序中有另一个线程可能会导致这种情况,因为其他线程可以分配对象并触发GC。测试时代码的所有者很容易错过这一点,要么是在使用多个线程时从未测试过它,要么就是没有足够幸运地在错误的时间触发GC。

正确的解决方法是确保抖动标记通过调用后正在使用的对象,以便垃圾收集器不会收集它。您可以在Execute()电话后添加GC.KeepAlive(calibrationValidator)

答案 1 :(得分:3)

在理解C#中的IDisposableGC.SuppressFinalize和终结器时,我认为不存在比下一篇文章更好的解释。

DG Update: Dispose, Finalization, and Resource Management

  

好的!这是:修订后的“Dispose,Finalization,and Resource Management”设计指南条目。我之前提到过这项工作herehere。在大约25个打印页面上,我认为这不是一个小的更新。花了我比预期更长的时间,但我对结果感到满意。我和HSutter,BrianGru,CBrumme,Jeff Richter和其他几个人一起工作并收到了很多反馈......好开心。

这个问题的关键概念:

显而易见 GC.SuppressFinalize() this只应在GC.SuppressFinalize()上调用该文章甚至不直接提及。但它确实提到了包装可终结对象以将它们与公共API隔离的做法,以确保外部代码无法在这些资源上调用{{1}}(请参阅以下引用)。设计原始问题中描述的库的人无法掌握.NET中的最终化方法。

引自博客文章:

  

即使没有上面提到的罕见情况之一,具有可公开访问引用的可终结对象也可以通过任意不受信任的调用者来终止其终结。具体来说,他们可以在您身上调用GC.SuppressFinalize并完全阻止完成,包括关键的终结。解决此问题的一个很好的缓解策略是将关键资源包装在具有终结器的非公共实例中。只要你不泄漏给呼叫者,他们就无法抑制终结。如果您在类中迁移到使用SafeHandle并且从不在课外公开它,那么您可以保证资源的最终确定(使用上面提到的警告并假设正确的SafeHandle实现)。

答案 2 :(得分:1)

有一些提到多线程或本机代码是导致此问题的原因。但同样的事情可能发生在一个纯粹的托管和大多数单线程程序中。

考虑以下计划:

using System;

class Program
{
    private static void Main()
    {
        var outer = new Outer();
        Console.WriteLine(outer.GetValue() == null);
    }
}

class Outer
{
    private Inner m_inner = new Inner();

    public object GetValue()
    {
        return m_inner.GetValue();
    }

    ~Outer()
    {
        m_inner.Dispose();
    }
}

class Inner
{
    private object m_value = new object();

    public object GetValue()
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        return m_value;
    }

    public void Dispose()
    {
        m_value = null;
    }
}

这里,在调用outer.GetValue()时,outer将被垃圾收集并最终确定(至少在发布模式下)。终结器会使Inner对象的字段无效,这意味着GetValue()将返回null

在实际代码中,您很可能不会在那里进行GC调用。相反,您将创建一些托管对象,(非确定性地)导致垃圾收集器运行。

(我说这段代码主要是单线程的。事实上,终结器将在另一个线程上运行,但由于对WaitForPendingFinalizers()的调用,它几乎就像在主线程上运行一样。)< / p>