[.net] 가비지 콜렉터가 IDisposable을 호출합니까?

닷넷 으로 IDisposable 패턴은 의미 당신이 종료자가 작성하고는 IDisposable을 구현하는 경우, 귀하의 종료 자 요구가 명시 적으로 폐기를 호출 할 수 있다는 것이다. 이것은 논리적 인 것이며, 파이널 라이저가 보증되는 드문 상황에서 항상 수행 한 것입니다.

그러나 내가 이렇게하면 어떻게됩니까?

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}

종료 자 또는 다른 것을 구현하지 마십시오. 프레임 워크에서 Dispose 메서드를 호출합니까?

예, 나는 이것이 멍청한 소리라는 것을 알고 모든 논리가 그렇지 않다는 것을 암시하지만 항상 머리 뒤쪽에 2 가지가있어서 확실하지 않았습니다.

  1. 몇 년 전에 누군가가 실제로 그렇게 할 것이라고 말했고 그 사람은 “자신의 지식을 알고있다”는 매우 탄탄한 기록을 가지고있었습니다.

  2. 컴파일러 / 프레임 워크는 구현하는 인터페이스 (예 : foreach, 확장 메소드, 속성을 기반으로하는 직렬화 등)에 따라 다른 ‘마법’을 수행하므로 이것이 ‘마법’일 수도 있습니다.

나는 그것에 대해 많은 것을 읽었으며 많은 암시가 있었지만 이 질문에 대한 확실한 예 또는 아니오 대답을 찾을 수 없었습니다 .



답변

.Net 가비지 콜렉터는 가비지 콜렉션에서 오브젝트의 Object.Finalize 메소드를 호출합니다. 하여 기본 이하지 않습니다 아무것도 하고 추가 자원을 확보하려는 경우 오버라이드 (override)해야합니다.

Dispose는 자동으로 호출되지 않으며 ‘using’또는 ‘finally’블록과 같이 리소스를 해제 해야하는 경우 명시 적으로 호출 해야합니다.

자세한 내용 은 http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx 를 참조하십시오.


답변

나는 그의 의견에서 Brian의 요점을 강조하고 싶습니다. 그것이 중요하기 때문입니다.

파이널 라이저는 C ++ 에서처럼 결정 론적 소멸자가 아닙니다. 다른 사람들이 지적했듯이,이 호출 될 때의 보장이없고, 당신이 충분한 메모리를 가지고 있다면 참으로 그것은한다면 지금까지 호출 할 수.

그러나 파이널 라이저의 나쁜 점은 Brian이 말했듯이 객체가 가비지 콜렉션에서 살아남는다는 것입니다. 이것은 나쁠 수 있습니다. 왜?

아시다시피, GC는 0 세대, 1 세대 및 2 세대와 대형 객체 힙으로 나뉩니다. 스플릿 (Split)은 느슨한 용어입니다. 한 블록의 메모리를 얻을 수 있지만 Gen 0 객체가 시작하고 끝나는 위치에 대한 포인터가 있습니다.

사고 과정은 수명이 짧은 많은 개체를 사용하게 될 것입니다. 따라서 GC가 Gen 0 객체에 쉽고 빠르게 도달 할 수 있어야합니다. 따라서 메모리 부족이있을 때 가장 먼저하는 일은 Gen 0 컬렉션입니다.

그래도 충분한 압력이 해결되지 않으면 다시 돌아가서 Gen 1 스윕 (Gen 0 다시 실행)을 수행 한 다음 여전히 충분하지 않은 경우 Gen 2 스윕 (Gen 1 및 Gen 0 다시 실행)을 수행합니다. 따라서 수명이 긴 개체를 정리하면 시간이 걸리고 비용이 많이들 수 있습니다 (작업 중에 스레드가 중단 될 수 있으므로).

이것은 당신이 이런 식으로하면 :

~MyClass() { }

개체가 무엇이든 관계없이 Generation 2에 적용됩니다. 이는 GC가 가비지 수집 중에 종료자를 호출 할 방법이 없기 때문입니다. 따라서 마무리해야하는 객체는 다른 스레드 (파이널 라이저 스레드-죽이면 모든 종류의 나쁜 일이 발생 함)에 의해 정리 될 수 있도록 특수 대기열로 이동됩니다. 즉, 객체가 오래 걸려서 가비지 수집이 더 많이 발생할 수 있습니다.

따라서 IDisposable을 사용하여 가능할 때마다 리소스를 정리하고 종료자를 사용하는 방법을 심각하게 찾으려고합니다. 응용 프로그램의 가장 큰 관심사입니다.


답변

여기에 이미 좋은 토론이 많이 있으며 파티에 약간 늦었지만 몇 가지 사항을 직접 추가하고 싶었습니다.

  • 가비지 콜렉터는 절대 Dispose 메소드를 직접 실행하지 않습니다.
  • GC 느낌이들 때 종료 자를 실행합니다.
  • 종료자가있는 객체에 사용되는 일반적인 패턴 중 하나는 Dispose (bool disposing)로 정의 된 메서드를 호출하여 명시 적 Dispose 호출이 아니라 종료로 인해 호출되었음을 나타냅니다.
  • 개체를 마무리하는 동안 다른 관리되는 개체에 대해 어떤 가정을하는 것은 안전하지 않기 때문입니다 (이미 마무리되었을 수 있음).

class SomeObject : IDisposable {
 IntPtr _SomeNativeHandle;
 FileStream _SomeFileStream;

 // Something useful here

 ~ SomeObject() {
  Dispose(false);
 }

 public void Dispose() {
  Dispose(true);
 }

 protected virtual void Dispose(bool disposing) {
  if(disposing) {
   GC.SuppressFinalize(this);
   //Because the object was explicitly disposed, there will be no need to 
   //run the finalizer.  Suppressing it reduces pressure on the GC

   //The managed reference to an IDisposable is disposed only if the 
   _SomeFileStream.Dispose();
  }

  //Regardless, clean up the native handle ourselves.  Because it is simple a member
  // of the current instance, the GC can't have done anything to it, 
  // and this is the onlyplace to safely clean up

  if(IntPtr.Zero != _SomeNativeHandle) {
   NativeMethods.CloseHandle(_SomeNativeHandle);
   _SomeNativeHandle = IntPtr.Zero;
  }
 }
}

그것은 간단한 버전이지만,이 패턴을 따라 잡을 수있는 많은 뉘앙스가 있습니다.

  • IDisposable.Dispose 계약은 여러 번 호출하는 것이 안전해야 함을 나타냅니다 (이미 삭제 된 개체에서 Dispose를 호출하면 아무 작업도 수행하지 않아야 함)
  • 다른 계층에서 새로운 일회용 및 관리되지 않는 리소스를 도입하는 경우 일회용 개체의 상속 계층 구조를 올바르게 관리하는 것이 매우 복잡해질 수 있습니다. 위의 패턴에서 Dispose (bool)은 가상으로 재정의되어 관리 할 수 ​​있으므로 오류가 발생하기 쉽습니다.

제 생각에는 일회용 참조와 마무리가 필요한 네이티브 리소스를 직접 포함하는 유형을 완전히 피하는 것이 좋습니다. SafeHandles는 내부 리소스를 내부적으로 자체적으로 마무리 할 수있는 일회용으로 캡슐화하여이를 수행하는 매우 깨끗한 방법을 제공합니다 (비동기 예외로 인해 기본 핸들이 손실 될 수있는 P / Invoke 중 창 제거와 같은 여러 다른 이점과 함께) .

SafeHandle을 정의하면 간단합니다.


private class SomeSafeHandle
 : SafeHandleZeroOrMinusOneIsInvalid {
 public SomeSafeHandle()
  : base(true)
  { }

 protected override bool ReleaseHandle()
 { return NativeMethods.CloseHandle(handle); }
}

포함 유형을 단순화하여 다음을 수행 할 수 있습니다.


class SomeObject : IDisposable {
 SomeSafeHandle _SomeSafeHandle;
 FileStream _SomeFileStream;
 // Something useful here
 public virtual void Dispose() {
  _SomeSafeHandle.Dispose();
  _SomeFileStream.Dispose();
 }
}


답변

나는 그렇게 생각하지 않습니다. Dispose가 호출되는 시점을 제어 할 수 있으므로 이론적으로 다른 개체의 존재에 대한 가정을하는 처리 코드를 작성할 수 있습니다. 종료자가 호출되는 시점을 제어 할 수 없으므로 종료자가 자동으로 Dispose를 호출하도록하는 것이 좋습니다.


편집 : 나는 그냥 가서 확인하기 위해 갔다 :

class Program
{
    static void Main(string[] args)
    {
        Fred f = new Fred();
        f = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Fred's gone, and he's not coming back...");
        Console.ReadLine();
    }
}

class Fred : IDisposable
{
    ~Fred()
    {
        Console.WriteLine("Being finalized");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("Being Disposed");
    }
}


답변

설명하는 경우가 아니라 GC는 Finalizer 가있는 경우 Finalizer 를 호출합니다 .

하나. 다음 가비지 수집은 수집되는 대신 객체가 마무리 큐에 들어가고 모든 것이 수집 된 다음 종료 자라고합니다. 그 이후의 다음 컬렉션은 해제됩니다.

앱의 메모리 압력에 따라 잠시 동안 해당 객체 생성을위한 gc가 없을 수 있습니다. 따라서 파일 스트림 또는 DB 연결의 경우 종료 자 호출에서 관리되지 않는 리소스가 잠시 동안 해제되어 잠시 동안 문제가 발생할 수 있습니다.


답변

아니요, 전화하지 않았습니다.

그러나 이렇게하면 물건을 처분하는 것을 잊지 않아도됩니다. using키워드를 사용하십시오 .

나는 이것을 위해 다음 테스트를 수행했다.

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo();
        foo = null;
        Console.WriteLine("foo is null");
        GC.Collect();
        Console.WriteLine("GC Called");
        Console.ReadLine();
    }
}

class Foo : IDisposable
{
    public void Dispose()
    {

        Console.WriteLine("Disposed!");
    }


답변

GC는 dispose를 호출 하지 않습니다 . 종료 자를 호출 할 수도 있지만 모든 상황에서 이것이 보장되는 것은 아닙니다.

이를 처리하는 가장 좋은 방법에 대한 설명은 이 기사 를 참조하십시오 .