2013-10-16 52 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时,它仍然不会释放信号量,因此不会释放信号量 –