我在实践中的观察是GC.SuppressFinalize
并不总是抑制对终结者的调用。可能是终结器被调用了。我想知道GC.SuppressFinalize
是否具有请求的性质而不是系统的保证?
更多信息
如果需要,以下信息可能有助于为问题提供更多背景信息。
GC.SuppressFinalize
文档摘要确实说明了这是一个请求:
请求系统不调用 指定对象的终结器。
我想知道这是偶然使用这个词还是真正用于描述运行时行为。
我观察到了以下来自Schnell项目的SingletonScope
课程,该课程基于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提供了一个完整的工作示例,其中包含编译说明,但请注意到目前为止无法确定性地再现问题。
答案 0 :(得分:4)
你可能会看到的一个奇怪的是,即使实例方法仍然在运行,终结器仍然可以运行,只要该实例方法稍后不使用任何变量。因此,在示例代码中,Dispose
方法在第一行之后不使用任何实例变量。然后可以最终确定实例,即使Dispose
仍在运行。
如果您在GC.KeepAlive(this)
方法的末尾插入对Dispose
的来电,则可能会发现问题消失。
克里斯·布鲁姆(Chris Brumme)有一个关于此问题的blog post,我认为还有其他地方......
答案 1 :(得分:3)
我总是使用这种设计模式来实现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.
}
}
答案 2 :(得分:1)
我在终结器中抛出一个InvalidOperationException,这样可以很容易地找到没有正确处理的类型。当调用Dispose()调用GC.SuppressFinalize时,我永远不会得到异常。
答案 3 :(得分:0)
我多次使用完全相同的模式,GC.SupressFinalize似乎总是起作用。
请记住,对GC.ReRegisterForFinalize的调用将导致对象重新注册以进行最终化。
每当我使用上面的技术时,我总是确保在对象构造期间包含完整的堆栈跟踪,这样我就可以跟踪分配非处置对象的方法。
EG。在构造函数中使用
StackFrame frame = new StackFrame(1);
并在终结期间在调试消息中报告。
另外,我注意到你的GC.SupressFinalize不在finally子句中,如果在dispose期间抛出异常,你的对象终结器将不会被抑制。
答案 4 :(得分:0)
当构造具有用户定义的终结器的对象时,运行时必须保留对它的内部引用,因此当它在用户代码中无法访问时,它仍然可以在运行时的终结线程中调用终结器。考虑到调用终结器时,时间是至关重要的,如果用户请求它们被抑制,则将对象保留在队列中是没有意义的。在我的测试CLI实现中,我在具有用户定义的终结器的对象的头部中保留了一个SuppressFinalizer标志。如果终结器线程到达队列中的该对象时该标志为true,则跳过终结器调用。我没有从队列中删除对象,所以我可以保持对GC.SuppressFinalize()
O(1)的调用,而不是O( N ),其中 N 是已分配的可终结对象的数量(我可能稍后将此策略更改为延迟删除策略)。