.Net Framework의 가비지 수집은 프로그래머가 관리되는 리소스를 자동으로 재활용하는 데 도움이 됩니다. 이는 클래스 라이브러리 호출자에게 매우 즐거운 경험입니다. 모든 객체는 언제 어디서나 생성될 수 있으며 GC는 이를 수행합니다. 항상 진실을 말할 것입니다. 우리가 클래스 라이브러리 제공자인 경우 어떻게 그렇게 좋은 경험을 제공할 수 있습니까?
먼저 .Net 프레임워크에서 관리되는 리소스는 무엇이고, 관리되지 않는 리소스는 무엇인가요?
기본적으로 .Net 프레임워크의 모든 클래스는 다양한 스트림(예: FileStream, MemoryStream), 데이터베이스 연결, 구성 요소 등을 포함하여 관리되는 리소스입니다. . 파일을 차지하는 다른 미사용 메소드를 작성하세요. FileStream은 단지 로컬 변수입니다. 이 메소드가 반환되면 재활용되어야 합니다.
마지막으로 필수 대기 시간이 있습니다.
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; }
조합은 테스트입니다.
먼저 파일 모니터링 스레드를 시작한 다음 사용하지 않고 파일을 엽니다.private static void OpenFile() { FileStream stream = File.Open(TestFileName, FileMode.Append, FileAccess.Write); Wait(fiveSeconds); }
그런 다음 GC를 호출하여 파일이 릴리스되었는지 확인합니다.
private static void Wait(TimeSpan time) { Console.WriteLine("Wait for {0} seconds...", time.TotalSeconds); Thread.Sleep(time); }
실행 결과를 보면 GC가 자동으로 FileStream을 재활용하는 것으로 나타났습니다. . Dispose 메소드를 호출할 필요도 없고, using
private static void FileTest() { MonitorFileStatus(TestFileName); OpenFile(); CallGC(); Wait(fiveSeconds); }
[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); }
위 예시와 같이 완성된 구현 코드는 다음과 같습니다.
같은 클래스에 관리되는 리소스와 관리되지 않는 리소스가 모두 있는 경우 어떻게 해야 할까요? 은 다음 패턴을 따를 수 있습니다.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 }
마지막으로 클래스는 어떻습니까? IDisposable 인터페이스를 구현하지 않습니까? 예를 들어 byte[], StringBuilder
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); } }
소멸자에서 거대한 byte[]를 null로 설정하려고 시도했지만 유일한 결과는 다음 GC 주기까지 수집이 지연된다는 것이었습니다.
이유도 매우 간단합니다. 참조가 이루어질 때마다 해당 참조 트리의 개수가 1씩 증가합니다. .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 }
전체 코드는 Github에서 찾을 수 있습니다:
https://github.com/IGabriel/IDisposableSample