.Net Framework 中的 Garbage Collection 會幫助程序員自動回收託管資源,這對類庫的調用者而言,是個至關愜意的體驗:能夠在任何位置,任什麼時候候,建立任何對象,GC 最後老是會兜底。
易地而處,當本身是類庫提供者的時候,則須要如何才能提供這樣良好的體驗呢?git
基本上,在 .Net framework 裏面的全部類,都是託管資源,包括各類各樣的 stream(例如 FileStream, MemoryStream), database connection, components 等等。。程序員
能夠寫一個簡單的小程序驗證:(以 FileStream 爲例)github
一個方法,在後臺線程中監控文件是否正在被佔用:小程序
private static void MonitorFileStatus(string fileName) { Console.WriteLine("Start to monitor file: {0}", fileName); Task.Factory.StartNew(() => { while(true) { bool isInUse = IsFileInUse(fileName); string messageFormat = isInUse ? "File {0} is in use." : "File {0} is released."; Console.WriteLine(messageFormat, fileName); Thread.Sleep(oneSeconds); } }); } private static bool IsFileInUse(string fileName) { bool isInUse = true; FileStream stream = null; try { stream = File.Open(fileName, FileMode.Append, FileAccess.Write); isInUse = false; } catch { } finally { if (stream != null) { stream.Dispose(); } } return isInUse; }
再寫一個佔着文件不用的方法, FileStream 只是個局部變量,這個方法返回的時候,它應該被回收:windows
private static void OpenFile() { FileStream stream = File.Open(TestFileName, FileMode.Append, FileAccess.Write); Wait(fiveSeconds); }
而後有一個收垃圾的 GCapi
private static void CallGC() { Console.WriteLine("Call GC.Collect..."); GC.Collect(); }
最後是一個必不可少的等待:app
private static void Wait(TimeSpan time) { Console.WriteLine("Wait for {0} seconds...", time.TotalSeconds); Thread.Sleep(time); }
合併起來就是一個測試:
首先啓動文件監視線程,而後打開文件不用。
OpenFile 方法返回,預測 FileStream 被回收
接着調用 GC, 看文件是否被釋放了函數
private static void FileTest() { MonitorFileStatus(TestFileName); OpenFile(); CallGC(); Wait(fiveSeconds); }
運行結果,可見 GC 自動把 FileStream 自動回收。無須調用 Dispose 方法,也無須使用 using
測試
一般,涉及到 windows api 的 pinvoke,各類的 intptr 都是非託管資源。
例如,一樣是打開文件,若是寫成如下的樣子,就包括了非託管資源ui
[Flags] internal enum OpenFileStyle : uint { OF_CANCEL = 0x00000800, // Ignored. For a dialog box with a Cancel button, use OF_PROMPT. OF_CREATE = 0x00001000, // Creates a new file. If file exists, it is truncated to zero (0) length. OF_DELETE = 0x00000200, // Deletes a file. OF_EXIST = 0x00004000, // Opens a file and then closes it. Used to test that a file exists OF_PARSE = 0x00000100, // Fills the OFSTRUCT structure, but does not do anything else. OF_PROMPT = 0x00002000, // Displays a dialog box if a requested file does not exist OF_READ = 0x00000000, // Opens a file for reading only. OF_READWRITE = 0x00000002, // Opens a file with read/write permissions. OF_REOPEN = 0x00008000, // Opens a file by using information in the reopen buffer. // For MS-DOS–based file systems, opens a file with compatibility mode, allows any process on a // specified computer to open the file any number of times. // Other efforts to open a file with other sharing modes fail. This flag is mapped to the // FILE_SHARE_READ|FILE_SHARE_WRITE flags of the CreateFile function. OF_SHARE_COMPAT = 0x00000000, // Opens a file without denying read or write access to other processes. // On MS-DOS-based file systems, if the file has been opened in compatibility mode // by any other process, the function fails. // This flag is mapped to the FILE_SHARE_READ|FILE_SHARE_WRITE flags of the CreateFile function. OF_SHARE_DENY_NONE = 0x00000040, // Opens a file and denies read access to other processes. // On MS-DOS-based file systems, if the file has been opened in compatibility mode, // or for read access by any other process, the function fails. // This flag is mapped to the FILE_SHARE_WRITE flag of the CreateFile function. OF_SHARE_DENY_READ = 0x00000030, // Opens a file and denies write access to other processes. // On MS-DOS-based file systems, if a file has been opened in compatibility mode, // or for write access by any other process, the function fails. // This flag is mapped to the FILE_SHARE_READ flag of the CreateFile function. OF_SHARE_DENY_WRITE = 0x00000020, // Opens a file with exclusive mode, and denies both read/write access to other processes. // If a file has been opened in any other mode for read/write access, even by the current process, // the function fails. OF_SHARE_EXCLUSIVE = 0x00000010, // Verifies that the date and time of a file are the same as when it was opened previously. // This is useful as an extra check for read-only files. OF_VERIFY = 0x00000400, // Opens a file for write access only. OF_WRITE = 0x00000001 } [StructLayout(LayoutKind.Sequential)] internal struct OFSTRUCT { public byte cBytes; public byte fFixedDisc; public UInt16 nErrCode; public UInt16 Reserved1; public UInt16 Reserved2; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string szPathName; } class WindowsApi { [DllImport("kernel32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true)] internal static extern IntPtr OpenFile([MarshalAs(UnmanagedType.LPStr)]string lpFileName, out OFSTRUCT lpReOpenBuff, OpenFileStyle uStyle); [DllImport("kernel32.dll", SetLastError = true)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [SuppressUnmanagedCodeSecurity] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CloseHandle(IntPtr hObject); }
處理非託管資源,須要實現 IDisposable interface。緣由有兩個:
不能依賴析構函數,由於異構函數的調用由 GC 決定。沒法實時釋放緊缺的資源。
有一通用的處理原則:析構函數處理託管資源,IDisposable interface 處理託管與非託管資源。
如上述的例子,完成的實現代碼以下:
public class UnmanagedFileHolder : IFileHolder, IDisposable { private IntPtr _handle; private string _fileName; public UnmanagedFileHolder(string fileName) { _fileName = fileName; } public void OpenFile() { Console.WriteLine("Open file with windows api."); OFSTRUCT info; _handle = WindowsApi.OpenFile(_fileName, out info, OpenFileStyle.OF_READWRITE); } #region IDisposable Support private bool disposed = false; protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { // no managed resource } WindowsApi.CloseHandle(_handle); _handle = IntPtr.Zero; disposed = true; } } ~UnmanagedFileHolder() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion }
能夠依照下面的模式:
class HybridPattern : IDisposable { private bool _disposed = false; ~HybridPattern() { Dispose(false); } protected void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { // Code to dispose the managed resources of the class // internalComponent1.Dispose(); } // Code to dispose the un-managed resources of the class // CloseHandle(handle); // handle = IntPtr.Zero; _disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } }
如下爲完整的例子,有託管的 FileStream, 以及非託管的 Handler
public class HybridHolder : IFileHolder, IDisposable { private string _unmanagedFile; private string _managedFile; private IntPtr _handle; private FileStream _stream; public HybridHolder(string unmanagedFile, string managedFile) { _unmanagedFile = unmanagedFile; _managedFile = managedFile; } public void OpenFile() { Console.WriteLine("Open file with windows api."); OFSTRUCT info; _handle = WindowsApi.OpenFile(_unmanagedFile, out info, OpenFileStyle.OF_READWRITE); Console.WriteLine("Open file with .Net libray."); _stream = File.Open(_managedFile, FileMode.Append, FileAccess.Write); } #region IDisposable Support private bool disposed = false; protected virtual void Dispose(bool disposing) { if (!disposed) { //Console.WriteLine("string is null? {0}", _stream == null); if (disposing && _stream != null) { Console.WriteLine("Clean up managed resource."); _stream.Dispose(); } Console.WriteLine("Clean up unmanaged resource."); WindowsApi.CloseHandle(_handle); _handle = IntPtr.Zero; disposed = true; } } ~HybridHolder() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion }
徹底不要插手干預它們的回收, GC 作得很好。
嘗試過在析構函數中把一個龐大的 byte[] 設置爲 null,惟一的結果是致使它的回收被延遲到下一次 GC 週期。
緣由也很簡單,每一次引用到會致使它的引用樹上的計數加一。。
完整代碼見 Github:
https://github.com/IGabriel/I...