2013-10-16 51 views
2

我正在實現一個類庫,並尋找一種方法來限制庫將分配給預設數量的給定類的實例數量。限制必須是機器範圍 - 一個簡單的static計數器是不夠的,因爲這隻會計算調用過程中的實例。我想保留儘可能簡單的東西(沒有內存映射文件等)並且儘可能安全(沒有在臨時文件或註冊表中存儲計數器),因此決定嘗試使用全局共享信號作爲「計數器」全局類實例計數(使用信號量)

public class MyClass : IDisposable 
{ 
    // Limit to 10 instances 
    Semaphore m_sem = new Semaphore(10, 10, "SharedName"); 

    public MyClass() 
    { 
    if(!m_sem.WaitOne(0)) 
    { 
     throw new Exception("No instances free"); 
    } 
    } 

    void IDisposable.Dispose() 
    { 
    m_sem.Release(); 
    } 
} 

這似乎工作正常。然而,如果Dispose()不被調用,信號量永遠不會被釋放 - 實質上是「泄漏」實例。現在恕我直言IDisposable是.NET最糟糕的部分之一 - 我看到遠遠比它更多的代碼using(...) {}。更糟糕的是,當您使用IDisposable作爲數據成員並且在您的應用程序的每個課程中都會看到「IDisposable癌症」傳播。

因此,我決定爲忘記using()的人實施完整的IDisposable(反模式)模式。

public class MyClass : IDisposable 
{ 
    // Limit to 10 instances 
    Semaphore m_sem = new Semaphore(10, 10, "SharedName"); 

    public MyClass() 
    { 
    if(!m_sem.WaitOne(0)) 
    { 
     throw new Exception("No instances left"); 
    } 
    } 

    ~MyClass() 
    { 
     Dispose(false); 
    } 

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

    void Dispose(bool disposing) 
    { 
     if(disposing) 
     { 
      m_sem.Release(); 
     } 
     else 
     { 
     // To release or not release? 
     // m_sem.Release(); 
     } 
    } 
} 

一切都簡單而具有using正確調用的時候,我鬆開semapore。但是,當被最終確定爲最後的手段時,據我瞭解,我不應該訪問受管資源,因爲銷燬順序不是固定的 - m_sem可能已被銷燬。

那麼,如何在用戶忘記using的情況下釋放信號? (RTFM可能是一個有效的答案,但我希望避免)。就目前而言,「泄漏」的實例直到最終使用我的程序集的過程終止(此時我假設全局信號量被釋放)

或者的確有沒有更好的方法來做到這一點?

+0

經過多次閱讀,即使MS自己的代碼似乎在這個問題上不一致。有時,實例在從終結器調用時,從不會在IDisposable數據成員上調用Dispose(),有時他們會這樣做。這一切都似乎基於MS自己的知識,包含什麼類的內幕正在做的事情 - 圍繞非託管的瘦託管包裝是安全的,而不是。還有另一個討厭IDisposable的原因,我猜想:)。 –

回答

2

有時,RTFM確實是答案。

我不一定會推薦這個,但你可以做的一件事就是固定信號量對象。例如:

public class MyClass : IDisposable 
{ 
    // Limit to 10 instances 
    Semaphore m_sem = new Semaphore(10, 10, "SharedName"); 
    private readonly GCHandle semHandle = GCHandle.Alloc(m_sem); 

    public MyClass() 
    { 
    if(!m_sem.WaitOne(0)) 
    { 
     throw new Exception("No instances free"); 
    } 
    } 

    void IDisposable.Dispose() 
    { 
    semHandle.Free(); 
    m_sem.Release(); 
    } 
} 

我不會這麼做,如果我有一大堆的這些對象,因爲釘住對象可以對垃圾收集器的效率產生負面影響。但據我所知,一個Normal固定對象不是一個問題(或不是一個問題)。

所有事情都考慮到了,我想我更喜歡RTFM方法。

+1

這是如何解決在未使用'使用'時確保釋放信號量的問題(或者Dispose否則未被調用)? –

+0

@PeterRitchie:我假設OP會在他完全實現Dispose模式時加入'GCHandle'技巧,這將允許信號量被放置在終結器中(實際上,當終結器調用Dispose時)。 –

+0

@JimMischel其實不是一個壞主意:)。我認爲,正如你所說,RTFM可能會更好。 –

0

在您的MyClass實例被垃圾收集之前,信號量將永遠不會GC'd,因爲MyClass的實例仍然有對它的引用。如果有參考,GC不會收集任何內容。一旦你的MyClass實例被正確定義(〜MyClass()函數返回時沒有錯誤),它不再有對信號量的引用,然後GC將收集它。

唯一值得關注的是垃圾收集命中,因爲它是非確定性的(因此可能永遠不會運行)。但你無法做任何事情。

此外,請確保在那裏有一個毯子catch (Exception e)子句。 GC線程的異常會導致一些時髦的東西。

+1

這並非完全正確。信號量實例可以在MyClass實例進入最終隊列後的任何時候收集。所以在終結器運行之前很有可能收集m_sem。 –

+0

這真的只是描述了問題,而不是解決方案(答案)。即當對象是GC'd時,它仍然不會釋放信號量,因此不會釋放信號量 –