全局类实例计数(使用信号量)

时间:2013-10-16 19:18:35

标签: c# multithreading

我正在实现一个类库,并且正在寻找一种方法来限制库将分配给预设数字的给定类的实例数。限制必须计算机范围 - 简单的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 cancer”时,您的应用中的每个课程都会传播。

所以我决定为那些忘记使用()的人实现完整的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可能是一个有效的答案,但我希望避免)。就目前而言,“泄露”实例一直计算,直到使用我的程序集的最终进程终止(此时我假设全局信号量被释放)

或者确实有更好的方法吗?

2 个答案:

答案 0 :(得分: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 :(得分:0)

在你的MyClass实例收集垃圾之前,信号量永远不会得到GC,因为你的MyClass实例仍然有引用它。如果有参考,GC不会收集东西。一旦你的MyClass实例正确完成(〜MyClass()函数返回没有错误),它就不再有对信号量的引用,然后GC会收集它。

唯一的问题是当垃圾收集命中时,因为它是非确定性的(因此可能永远不会运行)。但是你无能为力。

另外,请确保在那里有一个毯子catch (Exception e)子句。 GC线程上的异常会导致一些时髦的东西。