2013-08-01 4 views
3

갑자기 의존하는 중첩 클래스가 IDisposable을 구현하는 IDisposable 체인을 깨는 방법을 원했지만 해당 인터페이스가 합성 레이어를 리핑하지 않도록하고 싶습니다. 기본적으로 IObservable<T>에 약한 구독이 있습니다. 'SubscribeWeakly()'을 통해 내가 래퍼 인스턴스가 누출되지 않도록 정리할 때 정리하려고합니다. Observable이 실행되지 않습니다. 그것은 동기 였지만 나는 다른 것들에도 사용했다.안전하게 .net finalizer 내에서 처리하십시오.

또 다른 게시물에 비슷한 문제가 있었으며 answer은 기본적으로 최종 자의 소지품에 액세스 할 수 있다고 명시했습니다. 그러나 종료 자의 순서가 보장되지 않으므로 처리가 문제가 될 수 있습니다.

따라서, 나는 일회용품이 살아있게 보장 할 수있는 방법이 필요했기 때문에 파이널 라이저에 Dispose()을 부를 수있었습니다. 그래서 GCHandle을 보았습니다. C++이 핸들을 free'd하고 .NET의 컨트롤에 복합 라이프 타임이 돌아올 때까지 계속 유지하기 위해 핸들과 집계를 응용 프로그램 핸들로 가져 와서 관리 대상 객체를 유지하고 유지합니다. 메모리 관리자. C++에서 왔기 때문에 std::unique_ptr과 비슷한 동작이 좋을 것이라고 생각하여 AutoDisposer과 비슷한 것을 생각해 냈습니다. 멀리 갈 때 자원을 처리하는 데 필요한 클래스에서

public class AutoDisposer 
{ 
    GCHandle _handle; 

    public AutoDisposer(IDisposable disposable) 
    { 
     if (disposable == null) throw new ArgumentNullException(); 

     _handle = GCHandle.Alloc(disposable); 
    } 

    ~AutoDisposer() 
    { 
     try 
     { 
      var disposable = _handle.Target as IDisposable; 
      if (disposable == null) return; 
      try 
      { 
       disposable.Dispose(); 
      } 
      finally 
      { 
       _handle.Free(); 
      } 
     } 
     catch (Exception) { } 
    } 
} 

, 방금 _autoDisposables = new AutoDisposer(disposables) 같은 필드를 할당합니다. 그런 다음이 AutoDisposer은 포함 클래스가 수행 할 때와 같이 가비지 컬렉터에 의해 정리됩니다. 그러나이 기술로 어떤 문제점이 있는지 궁금합니다. 지금은 다음을 생각할 수 있습니다 가진 파이 나라에 의해 가비지 컬렉터에

  • 추가 오버 헤드
  • .NET 응용 프로그램 핸들로 관리되는 메모리에서 항목을 꺼내 필요하고 그들에게
  • 하지 반환의 추가 오버 헤드 IDisposable를 구현하는 부담을 너무 많이하지 않을 때 내가 필요하면 단위 테스트 가능한 (나는 자원이 메모리 관리를 위해 .NET으로 반환됩니다 예측할 수없는 것.) 따라서

, 내가 아껴서 사용 결정적으로에 전화하기등

다른 문제가 있습니까? 이 기술은 유효합니까?

+5

간단한 답 : 모든 소멸자 제거 (종료 자). 잊어 버려. –

+3

_ Dispos() _ _를 호출 할 수 있도록 일회용품이 생존하도록 보장하는 방법이 필요했습니다. 거꾸로 물건을 뒤집습니다. –

+1

당신이 * finalizer 내부에있을 때'IDisposable' (올바르게 구현 된 경우)은 이미 finalizer를 실행하고 finalizer 내에서 합리적으로 기대할 수있는만큼 정리를 수행했을 수 있습니다. 당신의 부름'Dispose'는이 단계에서 도움을 줄 수 없습니다. –

답변

3

난 당신이 오해 IDispoable를했습니다 것 같아요 - IDisposable 개체의 전형적인 패턴이 느슨하게입니다 : 당신이 그것을 실행을 기다리는 경우 파이널 항상 관리되지 않는 리소스를 처리해야

void Dispose() { Dispose(true); } 

void Dispose(bool disposing) 
{ 
    if (disposing) 
    { 
     // Free managed resources 
    } 
    // always free unmanaged resources 
} 

~Finalizer() { Dispose (false); } 

있기 때문에 (일 것이다 미래에 메모리 제약 조건이 가비지 콜렉션을 트리거하거나 수동으로 트리거되는 시점에서) 누출해서는 안됩니다. 해당 리소스가 해제 될 때 약 결정적이되고 싶다면 IDispoable을 클래스 계층 구조에 노출해야합니다.

+0

분명히 적절한 Dispose를 사용하는 것이 바람직하다. 문제는 내가 본 것처럼, Dispose를 호출하지 못한 코드에 의한 피해를 어떻게 제한 할 것인가이다. 예를 들어, 실패시 로그인해야하는 데이터를 버퍼링하는 "로그 임시"기능이있는 로깅 클래스가있을 수 있습니다.성공할 경우 해당 버퍼를 버리고 성공을 기록합니다. 그렇지 않으면 버퍼 내용과 함께 실패를 기록합니다. 그러한 객체가 포기 된 경우 로그 파일이 닫히기 전에 버려진 것을 나타내는 항목과 함께 버퍼를 출력하도록 할 수 있습니다. – supercat

+0

Dispose 패턴을 이해하고 있다고 생각합니다. @supercat이 내 의도를 묘사했습니다. 나는'Dispose'를 호출하지 못하는 코드에 의한 피해를 제한하려고합니다. 처리 할 관리되지 않는 리소스는 없습니다. Dispose는'Dispose'를 호출하지 않으면 발생하지 않는 액션 (Rx의'IObservable' 이벤트에서 이벤트 래퍼를 제거합니다)을 수행하고 있습니다. 이벤트 소스가 사라지면 괜찮습니다. 그렇지 않은 경우, 누출을 방지하기 위해 약한 (약한 의미의 약한 의미 인) 가입 래퍼를 등록 취소해야합니다. 그래서 나는'IDisposable'을 구현하지 않고 사라질 때'Dispose'를 호출하는 안전한 방법이 필요합니다. – moes

+0

Dispose는 자원을 결정 론적으로 정리하는 것에 관한 것입니다. 파이널 라이저가 호출 될 때까지 이미 GC가 해제되었으므로 파이널 라이저를 정의하면 더 오래 기다릴 수 있습니다. Finalizer 대기열에 있습니다. –

3

마무리 작업을 서로 조정할 수 있도록 클래스를 디자인 할 수 있습니다.예를 들어, 객체는 Action(bool) 유형의 생성자 매개 변수를 허용하고 null이 아닌 경우 Dispose(bool)의 첫 번째 단계로 호출 될 것이라고 지정합니다. Interlocked.Exchange(ref theField, null)으로 백킹 필드를 읽으면 최대 한 번 호출 할 수 있습니다 ]. 예를 들어, 그러한 기능에 포함 된 파일을 캡슐화하고 추가 버퍼링을 사용하여 파일을 캡슐화하는 클래스에 래핑 된 파일은 버퍼링 클래스에 닫기를 알리며 버퍼링 클래스는 필요한 모든 데이터가 기록되도록합니다. 불행히도 이러한 패턴은 프레임 워크에서는 일반적이지 않습니다.

이러한 패턴이 없으므로 버퍼링 된 파일을 캡슐화하는 클래스가 버려지면 파일을 닫을 수 있고 파일을 닫을 필요없이 데이터를 쓸 수 있다는 것을 보장 할 수있는 유일한 방법은 파일의 어딘가에 정적 참조를 유지하고 (아마도 ConcurrentDictionary(bufferedWrapper, fileObject)의 정적 인스턴스를 사용하여) 정리 된 경우 해당 정적 참조를 파괴하고 파일에 데이터를 기록한 다음 파일을 닫아야합니다. 랩퍼 오브젝트가 랩핑하는 오브젝트에 대해 독점적 인 제어를 유지하는 경우에만이 접근법을 사용해야하며, 세부 사항에 많은주의를 기울여야합니다. 파이널 라이저는 많은 이상한 코너 케이스를 가지고 있으며 제대로 처리하기 어렵습니다. 모호한 케이스를 올바르게 처리하지 못하면 Heisenbug가 생길 수 있습니다.

PS 당신이 이벤트 같은 것을 사용하는 경우 당신의 주요 관심사는 (1) 객체가 포기하면 이벤트가 "큰 아무것도에 대한 참조를 유지하지 않는 것이 보장 될 쉽다는 ConcurrentDictionary 접근 방식을 따라 계속 "; (2) ConcurrentDictionary에있는 버려진 객체의 수가 경계없이 성장할 수 없도록 보장합니다. 첫 번째 문제는 이벤트에서 상호 연결된 개체의 중요한 "포레스트"에 대한 "강력한"참조 경로가 없음을 보장함으로써 처리 할 수 ​​있습니다. 불필요한 서브 스크립 션이 100 바이트 정도의 오브젝트에 대한 참조만을 보유하고 있으며, 이벤트가 발생하면 취소 될 것입니다. 심지어 버려진 서브 스크립 션 수는 천천히 (사소한 문제가 있음) 나타납니다. 두 번째 문제는 각 구독 요청에서 사전에있는 일부 항목 (요청 당 또는 상각 기준)을 폴링하여 버려 졌는지 확인하고이를 포기한 경우 정리할 수 있습니다. 일부 이벤트가 중단되고 절대 실행되지 않고 해당 유형의 새 이벤트가 추가되지 않으면 해당 이벤트가 무기한으로 지속될 수 있지만 무해합니다. 이벤트가 크게 위험 할 수있는 유일한 방법은 큰 오브젝트 (약한 참조를 사용하지 않아도 됨)에 대한 참조를 보유한 경우 무제한 수의 이벤트를 정리하고 정리하지 않고 포기할 수 있다는 것입니다. 새로운 이벤트를 추가하면 버려진 이벤트가 추가되어 정리됩니다.) 또는 그러한 이벤트로 인해 CPU 시간이 계속 낭비 될 수있는 경우 (해당 이벤트를 돌보는 객체가 사라진 후 해당 객체를 시작한 첫 시도가 사라지면 발생하지 않습니다.).

+0

내 GCHandle은 일회용품을 안전하게 보관하여 마무리 작업을 안전하게 처리 할 수 ​​있도록 보장하지 않겠습니까? 이것은 C++에서 관리 객체가 네이티브 코드에 의해 유지되도록 보장하기 위해 사용되는 것입니다. 애플리케이션 종료시 문제가 발생할 수 있습니다. 그러나 그들은 C++을위한 것 같지 않습니다. 그래서 나는 마이크로 소프트가 그 문제를 해결했다고 가정합니다. (아마도 .NET은 이미 종료되었으므로 응용 프로그램이 메모리를 자유롭게 처리하고 정상적인 관리되지 않는 리소스를 버립니다). – moes

+0

ConcurrentDictionary 접근법을 너무 자주 실행하여 (빈약 한 참조를 사용하여 다른 항목의 수명과 연결된 항목에서 처리하도록) 자주 실행되는 스레드로 진행할 것을 고려했습니다. 응용 프로그램을 종료 할 때 물건을 처리하는 것에 대해 걱정할 필요가있는 종료자가 없습니다. 그러나 추가 스레드, "전역"변수 (사전)가 필요하며이를 정리하기위한 응용 프로그램 차원의 타이밍에 정착해야합니다. 'AutoDisposer' 방식은 좀 더 독립적 인 것처럼 보였습니다. – moes

+0

@moes : 위의 편집을 참조하십시오. – supercat