etc./StackOverFlow

IDisposable 인터페이스의 적절한 사용

청렴결백한 만능 재주꾼 2022. 1. 8. 01:38
반응형

질문자 :cwick


나는 읽고 알고 Microsoft 설명서를 의은 "차"를 사용한다는 IDisposable 인터페이스는 관리되지 않는 리소스를 정리하는 것입니다.

나에게 "관리되지 않는"은 데이터베이스 연결, 소켓, 창 핸들 등과 같은 것을 의미합니다. 그러나 Dispose() 메서드가 관리되는 리소스를 해제하기 위해 구현되는 코드를 본 적이 있습니다. 당신을 위해 그것을 돌봐.

예를 들어:

 public class MyCollection : IDisposable { private List<String> _theList = new List<String>(); private Dictionary<String, Point> _theDict = new Dictionary<String, Point>(); // Die, clear it up! (free unmanaged resources) public void Dispose() { _theList.clear(); _theDict.clear(); _theList = null; _theDict = null; }

MyCollection 사용하는 가비지 수집기의 여유 메모리를 평소보다 빠르게 만드는 것입니까?

편집 : 지금까지 사람들은 IDisposable을 사용하여 데이터베이스 연결 및 비트맵과 같은 관리되지 않는 리소스를 정리하는 몇 가지 좋은 예를 게시했습니다. _theList 에 백만 개의 문자열이 포함되어 있고 가비지 수집기를 기다리지 않고 지금 해당 메모리를 해제하려고 한다고 가정합니다. 위의 코드가 그것을 달성할 수 있을까요?



폐기의 포인트는 관리되지 않는 리소스를 해제하는 것입니다. 특정 시점에서 수행해야 합니다. 그렇지 않으면 절대 정리되지 않습니다. 가비지 수집기가 호출하는 방법을 모르는 DeleteHandle() 형식의 변수에 IntPtr , 그것은 호출해야하는지 여부를 알지 못한다 DeleteHandle() .

참고 : 관리되지 않는 리소스 란 무엇입니까? Microsoft .NET Framework에서 찾은 경우: 관리됩니다. MSDN을 직접 탐색했다면 관리되지 않습니다. P/Invoke 호출을 사용하여 .NET Framework에서 사용할 수 있는 모든 것의 안락한 세계에서 벗어나는 모든 것은 관리되지 않으며 이제 이를 정리할 책임이 있습니다.

생성한 개체는 관리되지 않는 리소스를 정리하기 위해 외부 세계에서 호출할 수 있는 일부 메서드를 노출해야 합니다. 메서드 이름은 원하는 대로 지정할 수 있습니다.

 public void Cleanup()

또는

 public void Shutdown()

그러나 대신 이 방법에 대한 표준화된 이름이 있습니다.

 public void Dispose()

IDisposable 이라는 하나의 메서드만 있는 인터페이스도 생성되었습니다.

 public interface IDisposable { void Dispose() }

따라서 개체가 IDisposable 인터페이스를 노출하도록 만들고 관리되지 않는 리소스를 정리하는 단일 메서드를 작성했다고 약속합니다.

 public void Dispose() { Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); }

그리고 당신은 끝났습니다. 당신이 더 잘할 수 있다는 것을 제외하고.


개체가 일종의 프레임 버퍼로 250MB System.Drawing.Bitmap (예: .NET 관리 Bitmap 클래스)을 할당했다면 어떻게 됩니까? 물론 이것은 관리되는 .NET 개체이며 가비지 수집기가 이를 해제합니다. 그러나 250MB의 메모리를 그냥 그대로 두고 싶습니까? 결국 가비지 수집기가 와서 메모리를 해제할 때까지 기다려야 합니까? 열린 데이터베이스 연결 이 있는 경우에는 어떻게 합니까? 확실히 우리는 GC가 객체를 완성하기를 기다리면서 그 연결이 열린 채로 있는 것을 원하지 않습니다.

Dispose() 를 호출한 경우(즉, 더 이상 개체를 사용할 계획이 없음을 의미) 왜 이러한 낭비적인 비트맵과 데이터베이스 연결을 제거하지 않습니까?

이제 우리는:

  • (우리가 해야 하기 때문에) 관리되지 않는 리소스를 제거하고
  • 관리 리소스 제거(도움이 되기를 원하기 때문에)

따라서 이러한 관리 객체를 제거하기 위해 Dispose() 메서드를 업데이트해 보겠습니다.

 public void Dispose() { //Free unmanaged resources Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //Free managed resources too if (this.databaseConnection != null) { this.databaseConnection.Dispose(); this.databaseConnection = null; } if (this.frameBufferImage != null) { this.frameBufferImage.Dispose(); this.frameBufferImage = null; } }

그리고 당신이 더 잘할 수 있다는 것을 제외하고 는 모든 것이 좋습니다!


사람이 개체에 대해 Dispose() 를 호출하는 것을 잊었다면 어떻게 될까요? 그러면 관리되지 않는 리소스가 누출됩니다!

참고: 결국 가비지 수집기가 백그라운드 스레드에서 실행되고 사용되지 않은 개체와 연결된 메모리를 해제하기 때문에 관리 리소스가 누출되지 않습니다. 여기에는 개체와 사용하는 관리 개체(예: BitmapDbConnection )가 포함됩니다.

Dispose() 를 호출하는 것을 잊었다면 우리는 여전히 그들의 베이컨을 저장할 수 있습니다! 가비지 수집기가 마침내 자유롭게 주위에 얻을 때 (즉, 마무리) 우리의 목적 : 우리는 여전히 그들을 위해 호출 할 수있는 방법이있다.

참고: 가비지 수집기는 결국 모든 관리 개체를 해제합니다. 그럴 때 Finalize 메서드를 호출합니다. GC의 알 또는 폐기 방법에 대한 배려하지 않는다. 그것은 우리가 관리되지 않는 것들을 제거하고 싶을 때 호출하는 메서드에 대해 선택한 이름일 뿐입니다.

Garbage Collector에 의한 객체 파괴는 이러한 성가신 관리되지 않는 리소스를 해제할 수 있는 완벽한 시간입니다. Finalize() 메서드를 재정의하여 이 작업을 수행합니다.

참고: Finalize() 메서드를 명시적으로 재정의하지 않습니다. C++ 소멸자 처럼 보이는 메서드를 작성 Finalize() 메서드의 구현으로 간주합니다.

 ~MyObject() { //we're being finalized (ie destroyed), call Dispose in case the user forgot to Dispose(); //<--Warning: subtle bug! Keep reading! }

하지만 그 코드에는 버그가 있습니다. 가비지 수집기는 백그라운드 스레드 에서 실행됩니다. 두 개체가 파괴되는 순서를 모릅니다. Dispose() 코드에서 제거하려는 관리 객체가 더 이상 존재하지 않을 가능성이 있습니다.

 public void Dispose() { //Free unmanaged resources Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle); //Free managed resources too if (this.databaseConnection != null) { this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it this.databaseConnection = null; } if (this.frameBufferImage != null) { this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it this.frameBufferImage = null; } }

따라서 필요한 것은 Finalize() Dispose()관리되는 리소스를 건드리지 않아야 함을 알리는 방법입니다(더 이상 존재하지 않을 수 있기 때문에). 동시에 관리되지 않는 리소스는 해제됩니다.

이를 수행하는 표준 패턴은 Finalize()Dispose() 모두 세 번째 (!) 메서드를 호출하도록 하는 것입니다. Dispose() Finalize() 와 반대)에서 호출하는 경우 부울 말을 전달하면 관리되는 리소스를 해제하는 것이 안전합니다.

내부 메서드 "CoreDispose" 또는 "MyInternalDispose"와 같은 임의의 이름을 지정할 수 있지만 Dispose(Boolean) 라고 부르는 전통입니다.

 protected void Dispose(Boolean disposing)

그러나 더 유용한 매개변수 이름은 다음과 같습니다.

 protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects) { //Free unmanaged resources Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //Free managed resources too, but only if I'm being called from Dispose //(If I'm being called from Finalize then the objects might not exist //anymore if (itIsSafeToAlsoFreeManagedObjects) { if (this.databaseConnection != null) { this.databaseConnection.Dispose(); this.databaseConnection = null; } if (this.frameBufferImage != null) { this.frameBufferImage.Dispose(); this.frameBufferImage = null; } } }

IDisposable.Dispose() 메서드의 구현을 다음과 같이 변경합니다.

 public void Dispose() { Dispose(true); //I am calling you from Dispose, it's safe }

종료자는 다음을 수행합니다.

 ~MyObject() { Dispose(false); //I am *not* calling you from Dispose, it's *not* safe }

참고 Dispose 를 구현하는 개체의 자손인 경우 Dispose를 재정의할 때 기본 Dispose 메서드를 호출하는 것을 잊지 마십시오.

 public override void Dispose() { try { Dispose(true); //true: safe to free managed resources } finally { base.Dispose(); } }

그리고 당신이 더 잘할 수 있다는 것을 제외하고 는 모든 것이 좋습니다!


사용자가 Dispose() 를 호출하면 모든 것이 정리된 것입니다. 나중에 가비지 수집기가 와서 Finalize를 호출하면 Dispose 다시 호출합니다.

Dispose() 대한 마지막 호출에서 이미 삭제한 개체에 대한 정크 참조가 개체에 있는 경우 다시 삭제하려고 합니다!

내 코드에서 내가 폐기한 개체에 대한 참조를 조심스럽게 제거했음을 알 수 있습니다. 따라서 정크 개체 참조에 대해 Dispose 하지만 미묘한 버그가 침투하는 것을 막지는 못했습니다.

사용자가 Dispose() 호출하면 CursorFileBitmapIconServiceHandle 핸들이 소멸됩니다. 나중에 가비지 수집기가 실행되면 동일한 핸들을 다시 파괴하려고 시도합니다.

 protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize) { //Free unmanaged resources Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy ... }

이 문제를 해결하는 방법은 가비지 수집기에 개체를 마무리하는 데 신경쓰지 않아도 된다고 알려주는 것입니다. 해당 리소스는 이미 정리되었으며 더 이상 작업이 필요하지 않습니다. Dispose() 메서드에서 GC.SuppressFinalize() 를 호출하여 이 작업을 수행합니다.

 public void Dispose() { Dispose(true); //I am calling you from Dispose, it's safe GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later }

이제 사용자가 Dispose() 호출했으므로 다음이 제공됩니다.

  • 관리되지 않는 리소스 해제
  • 해제된 관리 리소스

종료자를 실행하는 GC에는 의미가 없습니다. 모든 것이 처리됩니다.

Finalize를 사용하여 관리되지 않는 리소스를 정리할 수 없습니까?

Object.Finalize 대한 설명서는 다음과 같이 말합니다.

Finalize 메서드는 개체가 파괴되기 전에 현재 개체가 보유한 관리되지 않는 리소스에 대한 정리 작업을 수행하는 데 사용됩니다.

그러나 MSDN 설명서는 IDisposable.Dispose 대해서도 말합니다.

관리되지 않는 리소스 해제, 해제 또는 재설정과 관련된 애플리케이션 정의 작업을 수행합니다.

그래서 그것은 무엇입니까? 관리되지 않는 리소스를 정리할 수 있는 곳은 어디인가요? 정답은:

당신의 선택입니다! Dispose 선택합니다.

확실히 관리되지 않는 정리를 종료자에 배치할 수 있습니다.

 ~MyObject() { //Free unmanaged resources Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //AC# destructor automatically calls the destructor of its base class. }

문제는 가비지 수집기가 언제 개체를 마무리할지 알 수 없다는 것입니다. 관리되지 않고 필요하지 않으며 사용되지 않은 기본 리소스는 가비지 수집기가 결국 실행될 때까지 계속 남아 있습니다. 그런 다음 종료자 메서드를 호출합니다. 관리되지 않는 리소스 정리. Object.Finalize 의 문서는 다음을 지적합니다.

종료자가 실행되는 정확한 시간은 정의되지 않습니다. 클래스 인스턴스에 대한 리소스의 결정적 릴리스를 보장하려면 Close 메서드를 IDisposable.Dispose 구현을 제공합니다.

Dispose 를 사용하여 관리되지 않는 리소스를 정리하는 장점입니다. 관리되지 않는 리소스가 정리되는 시점을 알고 제어할 수 있습니다. 그들의 파괴는 "결정적" 입니다.


원래 질문에 대답하려면: GC가 하기로 결정할 때가 아니라 지금 메모리를 해제하지 않는 이유는 무엇입니까? 더 이상 필요하지 않기 때문에 지금 530MB의 내부 이미지를 제거 해야 하는 안면 인식 소프트웨어가 있습니다. 우리가하지 않을 때 : 기계는 교환을 멈추게합니다.

보너스 읽기

이 답변의 스타일을 좋아하는 사람은( , 어떻게 하면 명확해 지는지 설명함) Don Box의 Essential COM 중 1장을 읽을 것을 제안합니다.

35페이지에서 그는 바이너리 개체 사용의 문제를 설명하고 눈앞에서 COM을 발명합니다. COM의 이유 를 알게 되면 나머지 300페이지가 명확해지고 Microsoft의 구현에 대해 자세히 설명합니다.

개체나 COM을 다룬 적이 있는 모든 프로그래머는 최소한 첫 번째 장을 읽어야 한다고 생각합니다. 역대 최고의 설명입니다.

추가 보너스 읽기

당신이 알고 있는 모든 것이 잘못된 것일 때 Eric Lippert의 아카이브

따라서 올바른 종료자를 작성하는 것은 실제로 매우 어렵습니다. 제가 드릴 수 있는 최선의 조언은 시도하지 말라는 것 입니다.


Community Wiki

IDisposable using 문을 악용하고 관리되는 개체를 결정적으로 정리하는 쉬운 방법을 활용하는 데 자주 사용됩니다.

 public class LoggingContext : IDisposable { public Finicky(string name) { Log.Write("Entering Log Context {0}", name); Log.Indent(); } public void Dispose() { Log.Outdent(); } public static void Main() { Log.Write("Some initial stuff."); try { using(new LoggingContext()) { Log.Write("Some stuff inside the context."); throw new Exception(); } } catch { Log.Write("Man, that was a heavy exception caught from inside a child logging context!"); } finally { Log.Write("Some final stuff."); } } }

yfeldblum

Dispose 패턴의 목적은 관리되는 리소스와 관리되지 않는 리소스를 모두 정리하는 메커니즘을 제공하는 것이며 이러한 일이 발생하는 시기는 Dispose 메서드가 호출되는 방식에 따라 다릅니다. 귀하의 예에서 목록을 지워도 폐기되는 컬렉션에 영향을 미치지 않기 때문에 Dispose를 사용하는 것은 실제로 폐기와 관련된 작업을 수행하지 않습니다. 마찬가지로 변수를 null로 설정하는 호출도 GC에 영향을 미치지 않습니다.

Dispose 패턴을 구현하는 방법에 대한 자세한 내용은 이 기사를 볼 수 있지만 기본적으로 다음과 같습니다.

 public class SimpleCleanup : IDisposable { // some fields that require cleanup private SafeHandle handle; private bool disposed = false; // to detect redundant calls public SimpleCleanup() { this.handle = /*...*/; } protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { // Dispose managed resources. if (handle != null) { handle.Dispose(); } } // Dispose unmanaged managed resources. disposed = true; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } }

여기서 가장 중요한 메서드는 실제로 두 가지 다른 상황에서 실행되는 Dispose(bool)입니다.

  • disposing == true: 메서드가 사용자 코드에 의해 직접 또는 간접적으로 호출되었습니다. 관리되는 리소스와 관리되지 않는 리소스를 삭제할 수 있습니다.
  • disposing == false: 메서드는 종료자 내부에서 런타임에 의해 호출되었으며 다른 개체를 참조하면 안 됩니다. 관리되지 않는 리소스만 폐기할 수 있습니다.

단순히 GC가 정리를 처리하도록 하는 것의 문제는 GC가 수집 주기를 실행할 때를 실제로 제어할 수 없다는 것입니다(GC.Collect()를 호출할 수 있지만 실제로는 그렇지 않음). 필요 이상으로 오래. Dispose()를 호출해도 실제로 수집 주기가 발생하거나 어떤 식으로든 GC가 개체를 수집/해제하지 않습니다. 단순히 사용된 리소스를 보다 확실하게 정리하고 이 정리가 이미 수행되었음을 GC에 알리는 수단을 제공합니다.

IDisposable과 dispose 패턴의 요점은 즉시 메모리를 해제하는 것이 아닙니다. Dispose 호출이 실제로 즉시 메모리를 해제할 수 있는 기회는 disposing == false 시나리오를 처리하고 관리되지 않는 리소스를 조작할 때뿐입니다. 관리 코드의 경우 GC가 실제로 제어할 수 없는 컬렉션 주기를 실행할 때까지 메모리가 실제로 회수되지 않습니다(이미 언급한 GC.Collect() 호출은 좋은 생각이 아닙니다).

.NET의 문자열은 관리되지 않는 리소스를 사용하지 않고 IDisposable을 구현하지 않으므로 "정리"하도록 강제할 방법이 없기 때문에 시나리오가 실제로 유효하지 않습니다.


Scott Dorman

Dispose가 호출된 후 개체의 메서드에 대한 추가 호출이 없어야 합니다(객체는 Dispose에 대한 추가 호출을 허용해야 함). 따라서 질문의 예는 어리석은 것입니다. Dispose가 호출되면 개체 자체를 삭제할 수 있습니다. 따라서 사용자는 해당 전체 개체에 대한 모든 참조를 버려야 합니다(null로 설정). 그러면 내부에 있는 모든 관련 개체가 자동으로 정리됩니다.

관리/비관리에 대한 일반적인 질문과 다른 답변의 토론에 관해서는 이 질문에 대한 모든 답변이 관리되지 않는 리소스의 정의로 시작해야 한다고 생각합니다.

요약하면 시스템을 상태로 전환하기 위해 호출할 수 있는 함수가 있고 해당 상태에서 다시 불러오기 위해 호출할 수 있는 또 다른 함수가 있다는 것입니다. 이제 일반적인 예에서 첫 번째 함수는 파일 핸들을 반환하는 함수이고 두 번째 함수는 CloseHandle 대한 호출일 수 있습니다.

그러나 이것이 핵심입니다. 일치하는 기능 쌍이 될 수 있습니다. 하나는 국가를 건설하고 다른 하나는 국가를 무너뜨립니다. 상태가 구축되었지만 아직 철거되지 않은 경우 리소스의 인스턴스가 존재합니다. 적절한 시간에 분해가 발생하도록 준비해야 합니다. 리소스는 CLR에서 관리하지 않습니다. 자동으로 관리되는 유일한 리소스 유형은 메모리입니다. GC와 스택의 두 가지 종류가 있습니다. 값 유형은 스택에서(또는 참조 유형 내부에 연결하여) 관리되고 참조 유형은 GC에서 관리합니다.

이러한 함수는 자유롭게 인터리브되거나 완벽하게 중첩되어야 하는 상태 변경을 일으킬 수 있습니다. 상태 변경은 스레드로부터 안전할 수도 있고 그렇지 않을 수도 있습니다.

Justice의 질문에 있는 예를 보십시오. 로그 파일의 들여쓰기에 대한 변경 사항은 완벽하게 중첩되어야 합니다. 그렇지 않으면 모두 잘못됩니다. 또한 그들은 스레드로부터 안전하지 않을 것입니다.

관리되지 않는 리소스를 정리하기 위해 가비지 수집기를 사용할 수 있습니다. 그러나 상태 변경 기능이 스레드로부터 안전하고 두 상태가 어떤 방식으로든 겹치는 수명을 가질 수 있는 경우에만 가능합니다. 따라서 Justice의 리소스 예제에는 종료자가 없어야 합니다! 그것은 아무에게도 도움이 되지 않을 것입니다.

이러한 종류의 리소스의 경우 종료자 없이 IDisposable 종료자는 절대적으로 선택 사항입니다. 이것은 많은 책에서 생략되거나 언급되지 않습니다.

Dispose 가 호출되었는지 확인 using 문을 사용해야 합니다. 이것은 본질적으로 스택으로 라이드를 연결하는 것과 같습니다(종료자가 GC에 대해 using 하는 것처럼 스택에 대해 사용).

누락된 부분은 Dispose를 수동으로 작성하고 필드와 기본 클래스를 호출하도록 해야 한다는 것입니다. C++/CLI 프로그래머는 그렇게 할 필요가 없습니다. 컴파일러는 대부분의 경우 이를 대신하여 작성합니다.

내가 완벽하게 중첩되고 스레드로부터 안전하지 않은 상태에 대해 선호하는 대안이 있습니다(IDisposable을 피하면 IDisposable을 구현하는 모든 클래스에 종료자를 추가하는 것을 거부할 수 없는 누군가와 논쟁을 벌이는 문제를 피할 수 있습니다) .

클래스를 작성하는 대신 함수를 작성합니다. 이 함수는 다음을 호출할 대리자를 수락합니다.

 public static void Indented(this Log log, Action action) { log.Indent(); try { action(); } finally { log.Outdent(); } }

그리고 간단한 예는 다음과 같습니다.

 Log.Write("Message at the top"); Log.Indented(() => { Log.Write("And this is indented"); Log.Indented(() => { Log.Write("This is even more indented"); }); }); Log.Write("Back at the outermost level again");

전달되는 람다는 코드 블록 역할을 하므로 호출자가 더 이상 람다를 남용할 위험이 없다는 점을 제외하고 를 using 하는 것과 동일한 목적을 수행하도록 자신의 제어 구조를 만드는 것과 같습니다. 리소스 정리에 실패할 수 있는 방법은 없습니다.

이 기술은 리소스가 겹치는 수명을 가질 수 있는 종류인 경우 덜 유용합니다. 그 이유는 리소스 A를 빌드한 다음 리소스 B를 빌드한 다음 리소스 A를 종료하고 나중에 리소스 B를 종료할 수 있기를 원하기 때문입니다. 그렇게 할 수 없습니다. 사용자가 이와 같이 완벽하게 중첩되도록 강제한 경우. 하지만 그런 다음 IDisposable 을 사용해야 합니다(그러나 무료가 아닌 스레드 안전성을 구현하지 않는 한 종료자는 여전히 없음).


Daniel Earwicker

IDisposable을 사용하는 시나리오: 관리되지 않는 리소스 정리, 이벤트 구독 취소, 연결 닫기

IDisposable 구현에 사용하는 관용구( threadsafe 아님):

 class MyClass : IDisposable { // ... #region IDisposable Members and Helpers private bool disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { // cleanup code goes here } disposed = true; } } ~MyClass() { Dispose(false); } #endregion }

olli-MSFT

MyCollection 이 어쨌든 가비지 수집될 예정이라면 폐기할 필요가 없습니다. 그렇게 하면 CPU를 필요 이상으로 휘젓고 가비지 수집기가 이미 수행한 일부 미리 계산된 분석이 무효화될 수도 있습니다.

IDisposable 을 사용하여 관리되지 않는 리소스와 함께 스레드가 올바르게 삭제되었는지 확인하는 등의 작업을 수행합니다.

편집 Scott의 의견에 대한 응답으로 :

GC 성능 메트릭이 영향을 받는 유일한 경우는 [원문 그대로] GC.Collect() 호출이 수행될 때입니다."

개념적으로 GC는 개체 참조 그래프의 보기와 스레드의 스택 프레임에서 이에 대한 모든 참조를 유지 관리합니다. 이 힙은 상당히 크고 많은 메모리 페이지에 걸쳐 있을 수 있습니다. 최적화를 위해 GC는 페이지를 불필요하게 다시 스캔하지 않도록 자주 변경될 가능성이 없는 페이지에 대한 분석을 캐시합니다. GC는 페이지의 데이터가 변경될 때 커널로부터 알림을 수신하므로 페이지가 더럽고 재검색이 필요하다는 것을 알고 있습니다. 컬렉션이 Gen0에 있으면 페이지의 다른 항목도 변경될 가능성이 있지만 Gen1 및 Gen2에서는 그럴 가능성이 적습니다. 일화적으로, Silverlight 플러그인이 해당 플랫폼에서 작동하도록 하기 위해 GC를 Mac으로 이식한 팀의 경우 Mac OS X에서는 이러한 후크를 사용할 수 없었습니다.

불필요한 자원 폐기에 대한 또 다른 점: 프로세스가 언로딩되는 상황을 상상해 보십시오. 또한 프로세스가 얼마 동안 실행되었다고 상상해 보십시오. 해당 프로세스의 많은 메모리 페이지가 디스크로 스왑되었을 가능성이 있습니다. 적어도 그들은 더 이상 L1 또는 L2 캐시에 없습니다. 이러한 상황에서는 프로세스가 종료될 때 운영 체제에서 해제할 리소스를 '해제'하기 위해 모든 데이터와 코드 페이지를 메모리로 다시 스왑하기 위해 언로드하는 애플리케이션이 의미가 없습니다. 이는 관리되는 리소스와 관리되지 않는 특정 리소스에도 적용됩니다. 비백그라운드 스레드를 활성 상태로 유지하는 리소스만 삭제해야 합니다. 그렇지 않으면 프로세스가 활성 상태로 유지됩니다.

이제 정상 실행 중에 관리되지 않는 메모리 누수를 방지하기 위해 올바르게 정리해야 하는 임시 리소스가 있습니다( @fezmonkey 가 데이터베이스 연결, 소켓, 창 핸들을 가리킴). 이것들은 폐기해야 하는 종류의 것들입니다. 스레드를 소유하는 클래스를 생성하는 경우(그리고 소유라는 것은 생성했다는 의미이므로 최소한 내 코딩 스타일에 IDisposable 구현하고 스레드를 Dispose .

.NET 프레임워크는 IDisposable 인터페이스를 이 클래스를 삭제해야 한다는 신호(심지어 개발자에게 경고까지)로 사용합니다. IDisposable (명시적 인터페이스 구현 제외)을 구현하는 프레임워크의 유형은 생각할 수 없습니다.


Drew Noakes

예, 그 코드는 완전히 중복되고 불필요하며 가비지 수집기가 그렇지 않은 경우 수행하지 않을 작업을 수행하지 않습니다(MyCollection의 인스턴스가 범위를 벗어나면) 특히 .Clear() 호출.

편집에 대한 답변: 일종의. 내가 이렇게 하면:

 public void WasteMemory() { var instance = new MyCollection(); // this one has no Dispose() method instance.FillItWithAMillionStrings(); } // 1 million strings are in memory, but marked for reclamation by the GC

메모리 관리를 위해 다음과 기능적으로 동일합니다.

 public void WasteMemory() { var instance = new MyCollection(); // this one has your Dispose() instance.FillItWithAMillionStrings(); instance.Dispose(); } // 1 million strings are in memory, but marked for reclamation by the GC

이 순간에 정말로 정말로 메모리를 해제해야 하는 경우 GC.Collect() 호출하십시오. 하지만 여기서 할 이유가 없습니다. 필요할 때 메모리가 해제됩니다.


mqp

게시 한 예에서 여전히 "지금 메모리를 해제"하지 않습니다. 모든 메모리는 가비지 수집되지만 이전 세대 에서 메모리를 수집할 수 있습니다. 확인하려면 몇 가지 테스트를 실행해야 합니다.


프레임워크 디자인 지침은 지침이지 규칙이 아닙니다. 인터페이스가 주로 무엇을 위한 것인지, 언제 사용하는지, 어떻게 사용하는지, 언제 사용하지 않는지 알려줍니다.

IDisposable 활용 실패 시 간단한 RollBack() 코드를 읽은 적이 있습니다. 아래 MiniTx 클래스는 Dispose()에서 플래그를 확인하고 Commit 호출이 발생하지 않은 경우 자체적으로 Rollback 호출 코드를 훨씬 더 쉽게 이해하고 유지 관리할 수 있도록 간접 계층을 추가했습니다. 결과는 다음과 같았습니다.

 using( MiniTx tx = new MiniTx() ) { // code that might not work. tx.Commit(); }

나는 또한 타이밍/로깅 코드가 같은 일을 하는 것을 보았다. 이 경우 Dispose() 메서드는 타이머를 중지하고 블록이 종료되었음을 기록했습니다.

 using( LogTimer log = new LogTimer("MyCategory", "Some message") ) { // code to time... }

따라서 다음은 관리되지 않는 리소스 정리를 수행하지 않지만 IDisposable을 성공적으로 사용하여 보다 깨끗한 코드를 만드는 몇 가지 구체적인 예입니다.


Robert Paulson

지금 삭제 하려면 관리되지 않는 메모리를 사용하십시오.

보다:


franckspike

관리되지 않는 리소스 사용 또는 해제에 대한 일반적인 내용은 반복하지 않겠습니다. 그러나 나는 일반적인 오해로 보이는 것이 무엇인지 지적하고 싶습니다.
다음 코드가 주어졌을 때

공개 클래스 LargeStuff
  IDisposable 구현
  Private _Large as string()

  ' _Large를 의미하는 이상한 코드에는 이제 수백만 개의 긴 문자열이 포함되어 있습니다.

  Public Sub Dispose()는 IDisposable.Dispose를 구현합니다.
    _Large=아무것도
  엔드 서브

Disposable 구현이 현재 지침을 따르지 않는다는 것을 알고 있지만 모두가 아이디어를 얻길 바랍니다.
이제 Dispose가 호출되면 얼마나 많은 메모리가 해제됩니까?

답변: 없습니다.
Dispose를 호출하면 관리되지 않는 리소스를 해제할 수 있지만 관리되는 메모리를 회수할 수 없으며 GC만 이를 수행할 수 있습니다. 위의 내용이 좋은 생각이 아니라고 말하는 것이 아니라 위의 패턴을 따르는 것이 사실 여전히 좋은 생각입니다. Dispose가 실행되면 LargeStuff의 인스턴스가 여전히 범위 내에 있을 수 있지만 _Large에서 사용하고 있던 메모리를 회수하는 GC를 중지할 수 없습니다. _Large의 문자열도 0세대에 있을 수 있지만 LargeStuff의 인스턴스는 2세대일 수 있으므로 다시 메모리가 더 빨리 회수됩니다.
하지만 위에 표시된 Dispose 메서드를 호출하기 위해 종료자를 추가하는 것은 의미가 없습니다. 종료자가 실행될 수 있도록 메모리 회수가 지연됩니다.


pipTheGeek

IDisposable/using 콤보는 시스템 리소스 의 수명 을 제어하는 방법으로 주로 사용되는 것 외에도 (Ian , kudos!의 멋진 답변으로 완전히 다룹니다.) IDisposable/using 콤보를 사용하여 (중요한) 전역 리소스의 상태 변경 범위를 지정할 수도 있습니다. 콘솔 , 스레드 , 프로세스 , 응용 프로그램 인스턴스 와 같은 전역 개체 .

이 패턴에 대한 기사를 작성했습니다. http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

콘솔 색상 , 현재 스레드 문화 , Excel 응용 프로그램 개체 속성 등 자주 사용하는 전역 상태를 재사용 가능 하고 읽기 쉬운 방식으로 보호하는 방법을 보여줍니다.


Pragmateek

무엇이든 코드를 생략할 때보다 코드가 덜 효율적일 것으로 예상합니다.

Clear() 메서드를 호출하는 것은 불필요하며 Dispose가 호출하지 않으면 GC가 호출하지 않을 것입니다...


Arjan Einbu

주어진 코드 샘플은 IDisposable 사용에 대한 좋은 예가 아닙니다. 사전 지우기는 일반적으로 Dispose 메서드로 이동하면 안 됩니다. 사전 항목은 범위를 벗어나면 삭제되어 폐기됩니다. IDisposable 구현은 범위를 벗어난 후에도 해제/해제되지 않는 일부 메모리/처리기를 해제하는 데 필요합니다.

다음 예는 일부 코드와 주석이 있는 IDisposable 패턴에 대한 좋은 예를 보여줍니다.

 public class DisposeExample { // A base 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 Component component = new Component(); // Track whether Dispose has been called. private bool disposed = false; // 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. protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. if(!this.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; // Note disposing has been done. 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 the MyResource object. } }

CharithJ

Dispose() MyCollection 개체의 일반 GC로 인해 발생하지 않는 효과를 가질 있는 예제 코드에서 수행하는 작업이 있습니다.

_theList 또는 _theDict 에서 참조하는 개체가 다른 개체에서 참조되는 경우 List<> 또는 Dictionary<> 개체는 컬렉션 대상이 아니지만 갑자기 내용이 없게 됩니다. 예제와 같이 Dispose() 작업이 없는 경우 해당 컬렉션에는 여전히 콘텐츠가 포함됩니다.

물론 이것이 상황이라면 나는 그것을 깨진 디자인이라고 부를 것입니다. 나는 List<> Dispose() 작업이 완전히 중복되지 않을 수 있음을 지적하고 있습니다. 조각에 표시되지 않는 List<> 또는 Dictionary<>


Michael Burr

"관리되지 않는 리소스"에 대한 대부분의 토론에서 한 가지 문제는 용어를 실제로 정의하지 않지만 관리되지 않는 코드와 관련이 있음을 암시하는 것 같습니다. 많은 유형의 관리되지 않는 리소스가 관리되지 않는 코드와 상호 작용하는 것은 사실이지만 이러한 용어로 관리되지 않는 리소스를 생각하는 것은 도움이 되지 않습니다.

대신, 모든 관리 자원의 공통점을 인식해야 합니다. 모든 관리 자원은 외부 '사물'에게 자신을 대신하여 무언가를 하도록 요청하는 객체를 수반하고, 다른 '사물'은 손해를 입게 되며, 다른 엔터티는 추가 공지. 대상이 버려지고 흔적도 없이 사라진다면 더 이상 존재하지 않는 대상을 대신하여 행동을 변경할 필요가 없다고 외부 '사물'에 말할 수 있는 것은 없습니다. 결과적으로 '물건의 유용성은 영구적으로 줄어들 것입니다.

관리되지 않는 자원은 어떤 외부 '사물'이 대상을 대신하여 자신의 행동을 변경하기로 합의한 것을 나타냅니다. 이는 해당 대상이 버려지고 존재하지 않는 경우 외부 '사물'의 유용성을 쓸모 없게 손상시킬 것입니다. 관리되는 리소스는 이러한 계약의 수혜자이지만 폐기되는 경우 알림을 받도록 등록한 개체이며 이러한 알림을 사용하여 파괴되기 전에 업무를 정리합니다.


supercat

IDisposable 은 이벤트 구독을 취소하는 데 유용합니다.


Adam Speight

정의의 첫 번째. 나에게 관리되지 않는 리소스는 IDisposable 인터페이스를 구현하는 일부 클래스 또는 dll 호출을 사용하여 생성된 것을 의미합니다. GC는 이러한 개체를 처리하는 방법을 모릅니다. 예를 들어 클래스에 값 유형만 있는 경우 이 클래스를 관리되지 않는 리소스가 있는 클래스로 간주하지 않습니다. 내 코드의 경우 다음 사례를 따릅니다.

  1. 내가 만든 클래스가 관리되지 않는 리소스를 사용하는 경우 메모리를 정리하기 위해 IDisposable 인터페이스도 구현해야 합니다.
  2. 사용을 마치자마자 물건을 청소하십시오.
  3. 내 dispose 메서드에서 클래스의 모든 IDisposable 멤버를 반복하고 Dispose를 호출합니다.
  4. 내 개체가 이미 정리되었음을 가비지 수집기에 알리기 위해 내 Dispose 메서드에서 GC.SuppressFinalize(this)를 호출합니다. GC를 호출하는 것은 비용이 많이 드는 작업이기 때문에 수행합니다.
  5. 추가 예방 조치로 Dispose()를 여러 번 호출할 수 있도록 하려고 합니다.
  6. 언젠가 개인 멤버 _disposed를 추가하고 메서드 호출에서 개체가 정리되었는지 확인합니다. 그리고 정리된 경우 ObjectDisposedException 을 생성하십시오.
    다음 템플릿은 코드 샘플로 단어로 설명한 내용을 보여줍니다.

 public class SomeClass : IDisposable { /// <summary> /// As usually I don't care was object disposed or not /// </summary> public void SomeMethod() { if (_disposed) throw new ObjectDisposedException("SomeClass instance been disposed"); } public void Dispose() { Dispose(true); } private bool _disposed; protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing)//we are in the first call { } _disposed = true; } }


Yuriy Zaletskyy

관리되는 리소스와 관리되지 않는 리소스 모두에 IDisposable을 사용하는 방법에 대해 많은 답변이 바뀌었습니다. IDisposable이 실제로 어떻게 사용되어야 하는지에 대해 내가 찾은 최고의 설명 중 하나로 이 기사를 제안하고 싶습니다.

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

실제 질문의 경우; 많은 메모리를 차지하는 관리되는 개체를 정리하기 위해 IDisposable을 사용해야 하는 경우 짧은 대답은 no 입니다. 그 이유는 메모리를 보유하고 있는 객체가 범위를 벗어나면 수집할 준비가 되었기 때문입니다. 이 시점에서 참조된 모든 하위 개체도 범위를 벗어나 수집됩니다.

이에 대한 유일한 실제 예외는 관리되는 개체에 많은 메모리가 묶여 있고 일부 작업이 완료되기를 기다리는 해당 스레드를 차단한 경우입니다. 해당 호출이 완료된 후 필요하지 않을 객체의 경우 해당 참조를 null로 설정하면 가비지 수집기가 객체를 더 빨리 수집할 수 있습니다. 그러나 그 시나리오는 IDisposable의 사용 사례가 아니라 리팩토링이 필요한 잘못된 코드를 나타냅니다.


MikeJ

관리 리소스 폐기에 대한 가장 정당한 사용 사례는 GC가 그렇지 않으면 수집되지 않을 리소스를 회수할 준비를 하는 것입니다.

대표적인 예가 순환 참조입니다.

순환 참조를 피하는 패턴을 사용하는 것이 가장 좋은 방법이지만, 예를 들어 '부모'에 대한 참조가 있는 '자식' 개체로 끝나는 경우 참조하고 GC에 의존합니다. 또한 종료자를 구현한 경우에는 호출되지 않습니다.

이 문제를 해결하는 유일한 방법은 부모 참조를 자식에서 null로 설정하여 수동으로 순환 참조를 끊는 것입니다.

부모와 자식에 IDisposable을 구현하는 것이 가장 좋은 방법입니다. 부모에서 Dispose가 호출되면 모든 자식에서 Dispose를 호출하고 자식 Dispose 메서드에서 부모 참조를 null로 설정합니다.


controlbox

출처 : http:www.stackoverflow.com/questions/538060/proper-use-of-the-idisposable-interface

반응형