在终结器中调用GC.Collect是否合适?

时间:2018-02-08 21:01:53

标签: c# garbage-collection

我们有一个BitmapImage类,用于包装本机图像ID和System.Drawing.Bitmap并实现IDisposable。图像ID是作为我们使用的第三方成像SDK的一部分创建的,它使用整数来标识指向某些像素数据的“图像”。图像ID和位图都指向相同的基础像素数据。

在许多情况下,SDK很难正确使用 ,所以我们有一个外观层来抽象SDK并为TIF和PDF文档提供易于使用且无错误的API ,部分原因是为了保证尽快释放内存。有数百页的300 DPI多页图像很常见,因此应用程序中的内存容易很高。

我们目前正在GC.Collect方法中调用Dispose来立即释放内存。在对软件进行全面测试之后,这是在释放基础像素数据后立即释放大量内存的唯一方法,特别是在我们可能在文档中合并数百页的大型合并操作期间。它也是以这种方式实现的,因此开发人员不会错误地尝试使用GC.Collect来分散他们的代码,因为他们不应该真正地调用它。

我的问题分为两部分:

  • 当垃圾收集器调用终结器时,它是否也会立即释放内存,或者可能在此之前有很长一段时间?我们也应该在这里拨打GC.Collect吗?特别是在32位进程中,我们必须确保尽可能多地保留可用内存。
  • 我们使用的SDK是GdPicture,即使你的图形对象Dispose,它也不会处理像素数据或图像引用。它让它们四处走动,直到开发人员释放它们为止。我们需要保证,如果开发人员没有手动调用Dispose,那么资源就会被释放。是否适合在终结器中引用托管类,例如GraphicsObject.ReleaseImage(id)?我在某些地方读到你不应该调用除SafeHandle之类的静态方法之外的其他方法,但除非我们称之为内存,否则不会释放内存

2 个答案:

答案 0 :(得分:0)

我觉得有点乱。希望在共同理解中更清楚。

您无法管理GCollection的时间。

GC-n在以下情况下发生:系统物理内存不足+托管堆上已分配对象使用的内存超过可接受的阈值。在该过程运行时不断调整该阈值。调用+ GC.Collect方法。几乎在所有情况下,您都不必调用此方法,因为GC会持续运行。 GC.Collect方法主要用于独特的情况和测试。

GC优化引擎根据正在进行的分配确定执行收集的最佳时间。 Finalizer执行的确切时间未定义。要确保为类实例确定性地释放资源,请实现Close()方法或提供IDisposable.Dispose实现+两个对象的终结器不保证以任何特定顺序运行+未指定终结器运行的线程。如果有人忘记Dispose使用析构函数

  

〜YourClass(){Dispose(false);}

转变为:

  

protected override void Finalize(){try {... cleanup ...} finally {base.Finalize(); }}

  • 别忘了
  

YourClass:IDisposable

它是(“Finalizer”)用于在GC销毁对象之前对当前对象持有的非托管资源执行清理操作。方法受到保护,因此只能通过此类或派生类访问。默认情况下(使用〜时),GC会在回收内存之前自动调用对象的终结器。

GC为每个类型实例添加一个条目到内部结构终结队列 - 由GC控制的内部数据结构(队列)。终结队列包含托管堆中所有对象的条目,其最终化代码必须在GC可以回收其内存之前运行。当GC发现对象是垃圾时,它会扫描Finalization Queue,寻找指向这些对象的指针。如果找到指针,它将从Finalization Queue中删除并添加到Freachable Queue。对象不再被视为垃圾,其内存不会被回收。此时,GC已完成识别垃圾。特殊运行时线程清空Freachable Queue,执行每个对象的Finalize方法。通常,当Freachable Queue为空时,该线程会休眠。

对于具有析构函数的对象,您至少需要2个GCollection(这就是为什么在Dispose模式中使用GC.SuppressFinilise(this) - 你说GC不会将对象移动到Generation_1或Generation_2,该内存可能被回收为GC会合析构函数) + (这也意味着具有终结器的对象最小移动到Generation_1,GC很少检查10次(比Generaation_0),这意味着你不需要的对象在内存中停留的时间更长)< / p>

建议申请或

  

使用(...)

转换为

  

最后尝试{...} {XX.Dispose();}

或使用IntPtr结构为非托管资源正确实现 IDisposable 接口(它适用于不同的架构(32或64))。 不要建议使用SafeHandle(Interopservices),因为它只适用于Win32架构(SafeHandle的好处是你不需要实现IDisposable,因为它继承自CriticalFinalizerObject)

IDisposable的经典实现,当你不需要关心是否有人忘记Dispose对象时(对于那种情况你有~detructor):

  

适用于基类

public MyClass() { this.reader = new TextReader(); }

public class A : IDisposable   
{
    bool disposed = false;  // Flag: Has Dispose already been called?
    private Component handle;
    private IntPtr umResource;
    public void Dispose()   {      
                Dispose(true);   GC.SuppressFinalize(this);   }
    protected virtual void Dispose(bool disposing) {
                if (disposed) return;
                if (disposing) {            // Free managed resources
                        if (reader != null) reader.Dispose(); 
                        if (handle != null) handle.Close(); }
                if (umResource != IntPtr.Zero) { // Free unmanaged resources
                            umResource = IntPtr.Zero; }
                disposed = true;   }  
    ~A() { Dispose(false); }   } // Only if we have real unmanaged resources
  

对于派生类

public class B : A  {
        private bool disposed = false;
        protected override void Dispose(bool disposing)  {
                    if (disposed) return;
                    if (disposing) {  ... }     // To free any managed objects 
                                               // Free unmanaged resources
                    disposed = true;     
                    base.Dispose(disposing)}   }

处理false - 来自Finalizer的调用;处置true - 来自Dispose(来自你)

抱歉,忘了恢复:

** 使用GC.Collect强制系统尝试回收最大可用内存量。您还可以向GC指出要检查哪些代。但是我正在开始 - 你无法控制,即使你使用GC.Collect - 这不能保证任何事情(GC可能会覆盖你的电话等),但是你可以尝试它不会带来任何问题< / strong> **

答案 1 :(得分:-1)

作为一般规则,您不应该在生产代码中使用GC.Collect。它可以帮助调试(如果你想查看内存峰值是否是临时的)。但通常最好将GC留给它自己的设备。它尽可能地优化。即使它没有收集,它也在做一些有效的事情(等待应用程序关闭可能会使它的工作变得更加简单)。如果您想影响主要行为,请改为为应用程序更改GC策略。

问题的第二部分是Dipose/Finalize pattern。有两种情况:

  • 您只处理实现IDisposeable的内容。在这种情况下,您需要做的就是实现Dispose。它所需要做的就是转发&#34; Dispose&#34;订购任何包含它的Disposeable。
  • 您实际上处理的是Unamaged资源。在这种情况下,您应该首先实现Finalizer(因此至少GC可以在应用程序关闭/收集时清除它)。然后你提供Dispose函数作为额外的东西。但我总是把Finalizer放在第一位,因为它会被保证调用。

终结者和处置在很大程度上是不同的。如此相同通常只需用布尔开关编写Dispose函数即可。 Finalizer和Dispose中唯一不同的是&#34; relay&#34;部分:   - 如果适用,您始终将Dispose调用中继到包含的实例。因为这是Dispose的常用用例(中继调用Dispose())。如果我们都遵守它,那就更好了。   - 你永远不会将fianlizer调用转发给包含的is​​ntances。最终确定是在该实例和GC之间进行的。

大约95%的案件只需要处理。

在Finalizer中调用GC.Collect毫无意义。终结者是关于清理非托管资源。 GC不管理的那些。

GC Collect现在只是&#34;现在收集托管资源&#34;。它们几乎适用于相反的情况(托管与未管理的资源)。