가비지 콜렉터가 IDisposable을 호출합니까?


134

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

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

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

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

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

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

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

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

답변:


121

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

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

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


35
실제로, 나는 GC가 Object.Finalize를 호출하지 않는다고 믿지 않습니다. 객체는 효과적으로 종료자를 갖지 않는 것으로 결정되고 종료가 억제되어 객체가 종료 / 도달 가능한 큐에있을 필요가 없으므로보다 효율적입니다.
Jon Skeet

7
MSDN에 따르면 msdn.microsoft.com/en-us/library/… C #에서 Object.Finalize 메서드를 실제로 "재정의"할 수 없으며 컴파일러에서 오류를 생성합니다. 개체를 재정의하지 마십시오. 대신 소멸자를 제공하십시오. ; 즉, Finalizer로 효과적으로 작동하는 소멸자를 구현해야합니다. [이 허용 대답이기 때문에 단지 완성도를 위해 여기에 추가하고 가장 가능성이 읽을 수]
Sudhanshu 슈라을

1
GC는 Finalizer를 재정의하지 않는 개체에 대해서는 아무 것도 수행하지 않습니다. Finalization 대기열에 들어 가지 않으며 Finalizer가 호출되지 않습니다.
Dave Black

1
@dotnetguy-원래 C # 사양에 "소멸자"가 언급되어 있지만 실제로는 Finalizer라고하며 실제로는 "소멸자"가 관리되지 않는 언어에서 작동하는 방식과 완전히 다릅니다.
Dave Black

67

나는 그의 의견에서 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을 사용하여 가능할 때마다 리소스를 정리하고 종료자를 사용하는 방법을 심각하게 찾으려고합니다. 응용 프로그램의 가장 큰 관심사입니다.


8
가능하면 IDisposable을 사용하고 싶지만 dispose 메서드를 호출하는 종료자가 있어야한다는 데 동의합니다. 객체가 finalizer 대기열에 들어 가지 않도록 dispose 메서드를 호출 한 후 IDispose.Dispose에서 GC.SuppressFinalize ()를 호출 할 수 있습니다.
jColeson

2
세대는 1-3이 아닌 0-2로 번호가 매겨 지지만 게시물은 그렇지 않습니다. 그러나 객체에 의해 참조되는 객체 또는 해당 객체에 의해 참조되는 객체 등도 다른 세대에 대해 가비지 수집 (완료는 아니지만)으로부터 보호됩니다. 따라서 종료자가있는 객체는 종료에 필요하지 않은 항목에 대한 참조를 보유해서는 안됩니다.
supercat


3
"당신의 사물이 무엇이든 관계없이 2 세대에 살 것입니다." 이것은 매우 기본적인 정보입니다! 시스템을 디버깅하는 데 많은 시간을 절약 할 수 있었으며, 종료를 위해 "준비된"짧은 Gen2 객체가 많았지 만 힙 사용으로 인해 OutOfMemoryException이 발생하지 않았습니다. 비어있는 파이널 라이저를 제거하고 코드를 다른 곳으로 옮기면 (문제 해결) 문제가 사라지고 GC가로드를 처리 할 수있었습니다.
sharpener

@CoryFoy "당신의 개체는 2 세대에 영향을 줄 것입니다"이것에 대한 문서가 있습니까?
Ashish Negi

33

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

  • 가비지 콜렉터는 절대 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();
 }
}

1
SafeHandleZeroOrMinusOneIsInvalid 클래스는 어디에서 왔습니까? 내장 .net 유형입니까?
Orion Edwards

+1 for // 내 생각에, 일회용 참조와 마무리가 필요한 네이티브 리소스를 직접 포함하는 유형을 완전히 피하는 것이 훨씬 좋습니다. 마무리.
supercat


1
GC.SuppressFinalize이 예에서 호출 에 대해. 이러한 맥락에서 SuppressFinalize는 Dispose(true)성공적으로 실행 된 경우에만 호출해야합니다 . 경우 Dispose(true)마무리가 억제 된 후 어느 시점에서 실패하지만 모든 자원 (특히 관리되지 않는 것들)을 정리하기 전에, 당신은 아직 마무리가 가능한 한 정리로 수행하기 위해 발생합니다. 에 대한 GC.SuppressFinalize호출 Dispose()후 메소드 로 호출 을 이동하는 것이 Dispose(true)좋습니다. 프레임 워크 디자인 지침이 게시물을 참조하십시오 .
BitMask777

6

나는 그렇게 생각하지 않습니다. 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");
    }
}

폐기하는 동안 사용할 수있는 물체에 대해 가정하는 것은 특히 마무리 과정에서 위험하고 까다로울 수 있습니다.
Scott Dorman

3

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

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

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


1

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

그러나 이렇게하면 물건을 처분하는 것을 잊지 않아도됩니다. 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!");
    }

1
이것은 <code> using </ code> 키워드를 사용하지 않으면 호출되지 않는 방법의 예입니다.이 스 니펫에는 9 년의 생일이 있습니다!
penyaskito

1

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

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


0

IDisposable 에 대한 문서 는 예제 코드뿐만 아니라 동작에 대한 명확하고 자세한 설명을 제공합니다. GC는 Dispose()인터페이스 에서 메소드를 호출하지 않지만 객체의 종료자를 호출합니다.


0

IDispose를 구현하는 객체가있는 경우 개발자는 객체 using의 컨텍스트를 중심으로 키워드 를 구현 하거나 Dispose 메서드를 직접 호출해야합니다.

패턴의 안전은 Dispose () 메소드를 호출하는 종료자를 구현하는 것입니다. 그렇게하지 않으면 메모리 누수가 발생할 수 있습니다. 즉, COM 래퍼를 만들고 System.Runtime.Interop.Marshall.ReleaseComObject (comObject)를 호출하지 않으면 Dispose 메서드에 배치됩니다.

clr에는 finalizer가 포함 된 개체를 추적하고 GC에 의해 Finalizer 테이블에 저장하고 GC에 의해 일부 정리 휴리스틱이 시작될 때 호출하는 것 외에 Dispose 메서드를 자동으로 호출하는 마술은 없습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.