2014-01-07 1 views
3

일반 개요 내가 4 개 주 기능이 기본 API와 결합 할 필요가강제 종료자를 주문

:

void ActivateEngine(); 
int CreateModule(); 
void DestroyModule(int id); 
void TerminateEngine(); 

그리고 문서 ActivateEngineTerminateEngineCreateModule에 모든 통화를 둘러싸고해야한다고 주장하고 DestroyModule. 나는이 .NET 객체의 DllImport 속성을 사용하여 네이티브 API에 바인더 제본 즉 EngineModule을 모두 만든이 작업을 수행하려면

void foo() 
{ 
    ActivateEngine(); 

    int module1 = CreateModule(); 
    int module2 = CreateModule(); 

    ... 

    DestroyModule(module2); 
    DestroyModule(module1); 

    TerminateEngine(); 
} 

: 그 사용량이 뭔가를해야합니다.

Engine 개체는 싱글 톤으로 동작하며 ActivateEngineTerminateEngine에 바인딩됩니다.

Module 개체는 Engine 내에 많은 인스턴스를 만드는 데 사용되며 기본 API에서는 CreateModuleDestroyModule에 바인딩됩니다.

가 발생했습니다 문제는

나는 (내가하지 [I 그리고 돈을 즉, 사용자가 Engine에 대해 또는 객체의 수명에 대해 너무 많이 수행하지 않고 Modules을 직접 만들 수있는 방식으로 일을 구현 한 '원하지 않음' 더 이상 사용하지 않을 경우 사용자가 객체를 폐기하도록합니다.

이렇게하려면 을 모두 가리키고 Engine 개체에 WeakReference의 목록을 사용했습니다.

내 간단한 코드 here을 참조하십시오.

문제는 응용 프로그램의 종료, 종료 자 비 determistic 방법으로 전화를 WeakReference 목표는 Module의 종결 자 아직 호출되지 않은 경우에도 이미 null이되어 때 매개 변수 trackResurrection이 true로 설정되어 있다는 점이다. 내 경우

코드는 다음 로그 : 물론 부적절 순서입니다

ActivateEngine() ... 
CreateModule() ==> 0 ... 
CreateModule() ==> 1 ... 
DestroyModule(1) ... 
    Ooops ... Can't dispose registered module because WeakReference to it is already null ... 
    Ooops ... Can't dispose registered module because WeakReference to it is already null ... 
TerminateEngine() ... 
DestroyModule(0) ... 

합니다.

질문 모든 Module을 강제 할 방법

Engine 전에 완료 할?

최종 사용자가 Module 개체에서 Dispose 메서드를 호출하도록 강요하고 싶지 않으며 코드에서 더 이상 참조되지 않을 때 개체가 자동으로 사라질 수 있도록 만든 Module에 대한 강력한 참조를 유지하고 싶지 않습니다.예 : 나는 ConditionalWeakTable을 사용하여 다음과 같은 스레드 보았다

processing 
{ 
    var module = new Module(); 
    ... 
} 

foo() 
{ 
    processing(); 
    GC.Collect(); // If using strong references 'module' is gonna be kept alive (that's not smart) 
} 

:

하지만이 내 상황에서 할 수있는 방법을 이해하지 않습니다.

는 엔진 싱글 및 모듈 객체 모두를 통해 엔진을 종료 할 의무를 배포 : 일반적인 문제에 대한 해결책보다는 특정 상황에 대한 해결 방법의

+0

제공하신 간소화 된 [코드 링크] (https://gist.github.com/CitizenInsane/802bb4bd61c708f3402e)를 확인할 수 있습니까? – Rob

+0

@Rob 죄송합니다. [다음] (https://gist.github.com/CitizenInsane/06d898e3e3c0a380d6a7) 링크를 수정했습니다. (새 링크로 질문이 수정되었습니다) – CitizenInsane

+1

WeakReference가 어떻게 될지 잘 모릅니다. 유능한. 최소한 카운터 *, 간단한 정적 int가 필요합니다. 모든 CreateModule() 호출마다 증가시키고 모든 DetroyModule() 호출에 대해 감소시킵니다. 0에 도달하면 TerminateEngine()을 안전하게 호출 할 수 있습니다. –

답변

2

기타 답변 및 의견에 따르면 참조 카운팅의 일부 형식을 구현해야한다고 나와 있습니다. 여기에 내 시도 (당신이 대답을 게시 할 때이 작업을하고 있었다.), 아직 싱글 톤 Engine을 사용하고있다. (그렇게 할 필요가 없다. 최소한의 변경으로 정적 인 클래스로 만들 수있다.) 그러나 호출자 카운트가 1 또는 0에 각각 도달 할 때 API를 설정하거나 해체해야하는지 여부를 엔진에 알리려면 AddRefrence()ReleaseRefrence()으로 전화해야합니다.

ModuleDispose()을 호출하거나 클래스가 완료 될 때 참조를 해제 할 수 있습니다.

using System.Threading; 

namespace FinalizerOrder 
{ 
    using System; 
    using System.Collections.Generic; 
    using System.Diagnostics; 

    class Engine 
    { 
     private Engine() 
     { 
      //ActivateEngine() is no longer called here. 
     } 

     private readonly static Engine _singleton = new Engine(); //Now that the constructor is empty we can initialize immediately. 
     private readonly static object _syncLock = new object(); 
     private static volatile int _counter = 0; 

     public static Engine Singleton 
     { 
      get { return _singleton; } 
     } 

     public void AddRefrence() 
     { 
      lock (_syncLock) 
      { 
       _counter++; 
       if (_counter < 0) 
        throw new InvalidOperationException("ReleaseRefrence() was called more times than AddRefrence()"); 

       if(_counter == 1) 
        Debug.WriteLine("ActivateEngine() ..."); 
      } 
     } 

     public void ReleaseRefrence() 
     { 
      lock (_syncLock) 
      { 
       _counter--; 

       if (_counter < 0) 
        throw new InvalidOperationException("ReleaseRefrence() was called more times than AddRefrence()"); 

       if (_counter == 0) 
       { 
        Debug.WriteLine("TerminateEngine() ..."); 
       } 
      } 
     } 
    } 

    class Module : IDisposable 
    { 
     public Module() 
     { 
      Engine.Singleton.AddRefrence(); 

      _id = _counter++; 
      Debug.WriteLine("CreateModule() ==> {0} ...", _id); 

     } 

     private readonly int _id; 
     private static int _counter; 

     ~Module() 
     { 
      Dispose(false); 
     } 

     public void Dispose() 
     { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 

     private bool _disposed = false; 

     protected void Dispose(bool disposing) 
     { 
      if(_disposed) 
       return; 

      _disposed = true;     

      if (disposing) 
      { 
       //Nothing to do here, no IDisposeable objects. 
      } 

      Debug.WriteLine("DestroyModule({0}) ...", _id); 
      Engine.Singleton.ReleaseRefrence(); 
     } 
    } 

    internal class Program 
    { 
     private static void Main() 
     { 
      Test(); 
      GC.Collect(3, GCCollectionMode.Forced); 
      GC.WaitForPendingFinalizers(); 
      Test(); 

     } 

     private static void Test() 
     { 
      var module1 = new Module(); 
      var module2 = new Module(); 

      GC.KeepAlive(module2); 
      GC.KeepAlive(module1); 
     } 
    } 
} 
+0

참조 계산을 사용하여 솔루션을 작성해 주셔서 감사 드리며 ... 사이에 끼어 들게해서 죄송합니다. 문제의 해결 방법은 간단하고 간단합니다. – CitizenInsane

+0

모듈을 만든 순서와 반대 순서로 모듈을 릴리스하는 방법은 무엇입니까? – Servy

+0

@Servy OP에서 필자는'ActivateEngine()'이'CreateModule()'의 첫 번째 호출 전에 호출되어야하고'DestateModule (int)'이 호출 될 때마다 TerminateEngine() 생성 된 인스턴스. 'ActivateEngine()'과'TerminateEngine()'사이에'DestroyModule (int)'의 순서에 대해서는 아무 것도 보이지 않습니다. –

3

더.

Interlocked methods (또는 기본 동등 물)을 통해 업데이트하는 공유 카운터를 만듭니다. static volatile int 필드 또는 관리되지 않는 메모리 조각 일 수 있습니다.

int은 앱에서 유지 관리하는 엔진에 대한 '참조'수를 계산해야합니다. 모든 생성자에서 카운터를 원자 적으로 증가시키고 모든 종료 자에서 카운터를 감소시킵니다. 카운터를 0으로 줄이는 하나의 finalizer도 TerminateEngine()을 호출합니다 (공유 카운터를 해제합니다).

사용자가 Module 개체를 모두 가비지 수집 할 수 있지만 새로운 개체를 만들 수있게하려면 엔진 개체도 '참조'로 계산해야합니다. 모듈. 그렇지 않으면 엔진이 일찌기 파괴됩니다.

+0

@Christian에게 감사드립니다. WeakEvents를 사용하여 해결책을 찾았지만 현재 상황에서 사용할 수있는 좋은 해결 방법입니다. – CitizenInsane

3

나는 이것이 당신이 원하는 방식으로 접근 할 수 있다고 생각하지 않습니다. 마무리 순서는 비 결정적이므로 싱글 톤 Engine 또는 Module이 먼저 파이널 라이즈되는지 여부를 알 수 없습니다.

엔진 싱글 톤을 제거하는 것이 좋습니다 (원하는 경우 엔진을 정적 클래스로 유지하고 인스턴스를 허용하지 않고 엔진 초기화 및 종료를 수행하는 정적 메서드에만 사용). 모듈 및 @ Christian-Klauser에 의해 답변 라인을 따라 정적 원자 계수기를 사용하십시오 (Module 생성자에서 증가되고 종료 자에서 감소됨). 원자 카운터가 0에서 1로 바뀌면 엔진을 활성화 (내부 정적 엔진 메소드 호출을 통해) 할 수 있고 모듈 수가 0이되면 엔진을 종료 할 수 있습니다.

Module 클래스를 사용할 때 사용자가 using 메커니즘을 사용해야합니다.

+0

답장을 보내 주셔서 감사합니다. @Rob. 관심이있는 경우 WeakEvents를 사용하는 아래 솔루션을 참조하십시오. – CitizenInsane

1

FastSmartWeakEvent 패턴을 사용하여 종료되었습니다.

이것은 일반적인 해결책이며 읽기 쉽고 이해하기 쉽습니다.

업데이트 된 샘플 코드 here을 참조하십시오.

+0

Damned ... 다른 테스트 케이스로 작업 한 후에 이것은 함정이다 ... WeakEvents는 WeakReferences ... 그들은 파이널 라이저 순서를 예측할 수 없게 만든다. Christian의 제안과 @Scott의 구현은 확실히 가장 적합한 것이다. 지금까지 모든 테스트 케이스에 대해). – CitizenInsane