2009-04-27 48 views
6

我在實踐中觀察到GC.SuppressFinalize並不總是禁止對終結器的調用。可能是終結者被稱爲無關緊要的。因此我想知道GC.SuppressFinalize是否具有要求而不是保證的性質?GC.SuppressFinalize是否有保證?


更多信息

以下信息可能幫助,如果需要的quesiton提供更多的上下文。

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用匯編指令,但請注意,這個問題不能被確定性至今轉載。

+0

在Dispose方法中沒有Trace,我推測你確定它在finalizer之前被成功調用? – Groo 2009-04-27 08:43:46

+0

@格羅:是的,我確定除非在C#中使用。 :) – 2009-04-27 11:46:00

回答

0

我已經多次使用完全相同的模式,並且GC.SupressFinalize一直顯示工作。

請記住,調用GC.ReRegisterForFinalize將導致對象重新註冊以進行最終化。

每當我使用上述技巧時,總是確保在對象構造過程中包含完整的堆棧跟蹤,以便我可以追蹤分配未處置對象的方法。

例如,在構造函數中使用

StackFrame frame = new StackFrame(1); 

並在調試信息中報告終結者。

另外,我注意到你的GC.SupressFinalize不在finally子句中,如果在dispose期間引發異常,你的對象終結器不會被壓制。

+0

正如您所看到的,在隨附代碼中沒有對GC.ReRegisterForFinalize的調用。 – 2009-04-27 08:57:22

+0

我知道這只是試圖確保答案是完整的,你是否嘗試了堆棧調試技巧? – 2009-04-27 08:58:29

4

您可能會奇怪可能即使在實例方法仍在運行時終結器仍然可以運行,只要該實例方法稍後不使用任何變量即可。所以在你的示例代碼中,Dispose方法在第一行之後不使用任何實例變量。然後可以最終確定實例,即使Dispose仍在運行。

如果您在Dispose方法的末尾插入GC.KeepAlive(this)的呼叫,您可能會可能發現問題消失。

克里斯Brumme有blog post過這個問題,我認爲還有另一個某處大約...

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. 
    } 
} 

來源: MSDN: GC.SuppressFinalize Method

0

當與一個用戶定義的終結一個對象被構造,運行時必須保持的內部引用它,以便當它成爲在用戶代碼不可到達它可以仍然具有在運行時的終結線程中調用終結器。考慮到調用終結器時,時間是關鍵,如果用戶請求壓縮對象,將對象保留在隊列中是沒有意義的。在我的測試CLI實現中,我在擁有用戶定義的終結器的對象頭中保留了一個SuppressFinalizer標誌。如果當終結器線程到達隊列中的該對象時該標誌爲真,則終止器調用將被跳過。我不會從隊列中刪除對象,所以我可以保持對GC.SuppressFinalize() O(1)而不是O(N)的呼叫,其中N是分配的可終結對象的數量(我可能會將此策略更改爲稍後推遲刪除政策)。

1

我在終結器中拋出了一個InvalidOperationException,它可以很容易地找到沒有正確處理的類型。當調用Dispose()時,GC.SuppressFinalize被調用,我永遠不會得到異常。

相關問題