【问题标题】:Is GC.SuppressFinalize guaranteed?GC.SuppressFinalize 有保证吗?
【发布时间】:2010-10-22 00:22:54
【问题描述】:

我在实践中的观察是GC.SuppressFinalize 并不总是抑制对终结器的调用。尽管如此,终结器可能会被调用。因此我想知道GC.SuppressFinalize是否具有请求的性质,而不是系统的保证


更多信息

如果需要,以下信息可能有助于为问题提供更多背景信息。

GC.SuppressFinalize 文档摘要确实表明这是一个请求:

请求系统不调用 指定对象的终结器。

我想知道这是对这个词的随意使用还是真的打算描述运行时行为。

我观察到以下SingletonScope 类取自Schnell 项目,它基于original idea by Ian Griffiths,但更通用。这个想法是在调试版本中检测Dispose 方法是否被调用。如果没有,终结器最终会启动,并且可以发出警告。如果Dispose 被调用,那么GC.SuppressFinalize 应该阻止终结器触发。不幸的是,警告似乎无论如何都会触发,但不是以一种确定性的方式。也就是说,它们不会在每次运行时都开火。

#region License, Terms and Author(s)
//
// Schnell - Wiki widgets
// Copyright (c) 2007 Atif Aziz. All rights reserved.
//
//  Author(s):
//      Atif Aziz, http://www.raboof.com
//
// This library is free software; you can redistribute it and/or modify it 
// under the terms of the GNU Lesser General Public License as published by 
// the Free Software Foundation; either version 2.1 of the License, or (at 
// your option) any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
// License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, 
// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
#endregion

namespace WikiPad
{
    #region Imports

    using System;
    using System.Diagnostics;

    #endregion

    //
    // NOTE: To use SingletonScope and ISingletonScopeHelper with value 
    // types, use Nullable<T>. For example, if the type of value to scope
    // is ThreadPriority then use ISingletonScopeHelper<ThreadPriority?>
    // and SingletonScope<ThreadPriority?>.
    //

    //
    // In debug builds, this type is defined as a class so a finalizer
    // can be used to detect an undisposed scope.
    //

    /// <summary>
    /// Designed to change a singleton and scope that change. After exiting
    /// the scope, the singleton is restored to its value prior to entering
    /// the scope.
    /// </summary>

    #if !DEBUG
    internal struct SingletonScope<T, H> 
    #else
    internal sealed class SingletonScope<T, H> 
    #endif
        : IDisposable 
        where H : ISingletonScopeHelper<T>, new()
    {
        private T _old;

        public SingletonScope(T temp)
        {
            _old = Helper.Install(temp);
        }

        private static H Helper
        {
            get { return new H(); }
        }

        public void Dispose()
        {
            //
            // First, transfer fields to stack then nuke the fields.
            //

            var old = _old;
            _old = default(T);

            //
            // Shazam! Restore the old value.
            //

            Helper.Restore(old);

            #if DEBUG
            GC.SuppressFinalize(this); // Only when defined as a class!
            #endif
        }

        #if DEBUG

        //
        // This finalizer is used to detect an undisposed scope. This will
        // only indicate that the scope was not disposed but (unfortunately)
        // not which one and where since GC will probably collect much later
        // than it should have been disposed.
        //

        ~SingletonScope()
        {
            Debug.Fail("Scope for " + typeof(T).FullName + " not disposed!");
        }

        #endif
    }
}

http://gist.github.com/102424 上提供了完整的工作示例以及编译说明,但请注意,到目前为止,该问题无法确定地重现。

【问题讨论】:

  • Dispose 方法中没有 Trace,我想你确定它在终结器之前被成功调用?
  • @Groo:是的,我敢肯定,除非在 C# 中使用被破坏。 :)

标签: c# garbage-collection


【解决方案1】:

可能看到的一个奇怪之处是,即使实例方法仍在运行,终结器仍然可以运行,只要该实例方法以后不使用任何变量。因此,在您的示例代码中,Dispose 方法在第一行之后不使用任何实例变量。然后可以完成实例,即使 Dispose 仍在运行。

如果您在Dispose 方法的末尾插入对GC.KeepAlive(this) 的调用,您可能会发现问题消失了。

Chris Brumme 对此有一个blog post,我认为在某个地方还有另一个...

【讨论】:

  • 乔恩,我很清楚这个警告,但是当应用程序存在时,其中的终结器和 Debug.Fail 代码似乎正在触发,我想正在执行挂起的终结器。这发生在 GC.SuppressFinalize 被调用后 long
  • @Jon 一些破坏 Dispose 的示例代码会很有启发性,我想知道是否可以将示例复杂起来。
  • @sambo99:我现在没有时间写一个示例,但我稍后会尝试这样做。基本上,如果你让 Dispose 调用 GC.Collect()/GC.WaitForPendingFinalizers,你可以看到它在 Dispose 完成之前完成实例。
  • 好吧,如果您从未在您的自包含示例中看到它,我不会尝试详细查看它。如果自包含示例有效,而应用程序无效,那么听起来两者之间存在显着差异......
  • 我不知道有什么说它是铸铁保证,但在这种情况下,我肯定会怀疑框架之前的应用程序。从可终结队列中删除项目非常简单——虽然在你调用 SuppressFinalize 之前终结器线程已经决定了它需要处理的内容时谨慎行事是合理的,但听起来你并没有那种比赛这里的条件,鉴于你所说的它正在关闭应用程序。
【解决方案2】:

我一直使用这种设计模式来实现 IDisposable 接口。 (这是微软建议的),对我来说 GC.SuppressFinalize 总是有保证的性质!

using System;
using System.ComponentModel;

//The following example demonstrates how to use the GC.SuppressFinalize method in a resource class to prevent the clean-up code for the object from being called twice.

public class DisposeExample
{
    // A class that implements IDisposable.
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources.
    public class MyResource : IDisposable
    {
        // Pointer to an external unmanaged resource.
        private IntPtr handle;
        // Other managed resource this class uses.
        private readonly Component component = new Component();
        // Track whether Dispose has been called.
        private bool disposed;

        // The class constructor.
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable.
        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue 
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed.
        private void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here.
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;
            }
            disposed = true;
        }

        // Use interop to call the method necessary  
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code.
        // This destructor will run only if the Dispose method 
        // does not get called.
        // It gives your base class the opportunity to finalize.
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }
    }

    public static void Main()
    {
        // Insert code here to create
        // and use a MyResource object.
    }
}

来源: MSDN: GC.SuppressFinalize Method

【讨论】:

  • 宾果游戏!您的实现不关心终结器是否在面对终结器抑制时被调用,因为它是防御性编码的。您的终结器回调 Dispose 并有一个额外的标志来确定它是否已被释放。我在我的终结器中对 Debug.Fail 进行了裸调用,假设如果我使用 GC.SuppressFinalize 将永远不会调用它,因此我对运行时的保证提出了疑问。
  • 如果你运行我的代码,你会看到析构函数(终结器)只有在 Dispose 方法没有被调用时才会运行。
  • 如果一个对象排队等待完成,而复活跟踪 WeakReference 存在,并且 WeakReference 被转换回一个强引用,那么代码是否有任何理由不能在同一个句柄上调用 CloseHandle 两次在正确的时间得到处置?在处理处置标志时,我倾向于使用 IInterlocked.Exchange 或 Interlocked.CompareExchange 以避免这种情况。
【解决方案3】:

我在终结器中抛出了一个 InvalidOperationException ,这样可以很容易地找到没有正确处理的类型。在调用 GC.SuppressFinalize 的地方调用 Dispose() 时,我从来没有遇到异常。

【讨论】:

    【解决方案4】:

    我已经多次使用完全相同的模式,而且 GC.SupressFinalize 似乎总是有效。

    请记住,对 GC.ReRegisterForFinalize 的调用将导致对象重新注册以进行终结。

    每当我使用上述技术时,我始终确保在对象构造期间包含完整的堆栈跟踪,以便我可以追踪分配未处置对象的方法。

    例如。在构造函数中使用

    StackFrame frame = new StackFrame(1);
    

    并在终结器期间在您的调试消息中报告。

    另外,我注意到您的 GC.SupressFinalize 不在 finally 子句中,如果在处置期间抛出异常,您的对象终结器将不会被抑制。

    【讨论】:

    • 如您所见,随附代码中没有调用 GC.ReRegisterForFinalize。
    • 我知道那只是为了确保答案是完整的,您尝试过堆栈帧调试技巧吗?
    • @sambo99 我会考虑添加这个技巧。同时,在这种情况下它也无济于事,因为我确定调用了 Dispose,因为范围对象与 C# 中的 using 结合使用。我想知道我是否需要在终结器中更加防御。
    • @Atif,你确定 dispose 没有抛出异常吗?
    • 您是否有可能创建一个失败的小样本?
    【解决方案5】:

    当一个具有用户定义的终结器的对象被构​​造时,运行时必须保持对它的内部引用,因此当它在用户代码中变得不可访问时,它仍然可以在运行时的终结线程中调用终结器。考虑到调用终结器时时间很重要,如果用户要求禁止对象,则将对象保留在队列中是没有意义的。在我的测试 CLI 实现中,我在具有用户定义终结器的对象的标头中保留了一个 SuppressFinalizer 标志。如果当终结器线程到达队列中的该对象时标志为真,则将跳过终结器调用。我没有从队列中删除对象,所以我可以继续调用GC.SuppressFinalize() O(1) 而不是 O(N),其中 N 是数字已分配的可终结对象(我以后可能会将此策略更改为延迟删除策略)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-11
      • 2015-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-24
      • 2018-05-19
      相关资源
      最近更新 更多