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

C# 全局类实例计数(使用信号量),c#,multithreading,C#,Multithreading,我正在实现一个类库,并寻找一种方法来限制库将分配给预设数量的给定类的实例数。限制必须是机器范围内的-一个简单的静态计数器是不够的,因为这将只计算调用过程中的实例。我想让事情尽可能简单(没有内存映射文件等)和尽可能安全(没有在临时文件或注册表中存储计数器),因此决定尝试使用全局共享信号量作为“计数器” 这似乎行得通。但是,如果没有调用Dispose(),信号量将永远不会被释放-实际上是“泄漏”实例。现在IMHOIDisposable是.NET最糟糕的部分之一-我看到使用(…){}比使用它时丢失的代

我正在实现一个类库,并寻找一种方法来限制库将分配给预设数量的给定类的实例数。限制必须是机器范围内的-一个简单的静态计数器是不够的,因为这将只计算调用过程中的实例。我想让事情尽可能简单(没有内存映射文件等)和尽可能安全(没有在临时文件或注册表中存储计数器),因此决定尝试使用全局共享信号量作为“计数器”

这似乎行得通。但是,如果没有调用
Dispose()
,信号量将永远不会被释放-实际上是“泄漏”实例。现在IMHO
IDisposable
是.NET最糟糕的部分之一-我看到使用(…){}比使用它时丢失的代码多得多。更糟糕的是,当你使用一个
IDisposable
作为数据成员,看着“IDisposable癌症”在你应用程序的每个类中蔓延

因此,我决定为忘记使用()的人实现完整的
IDisposable
(反)模式

当使用
正确调用时,事情很简单,我释放了semapore。但是,当最终确定作为最后手段时,据我所知,我不应该访问托管资源,因为销毁顺序不是固定的-m_sem可能已被销毁

那么,在用户忘记使用
时,如何释放信号量呢?(RTFM可能是一个有效的答案,但我希望避免)。目前,“泄漏”实例一直计数,直到使用我的程序集的最后一个进程终止为止(此时我假设全局信号量被释放)


或者确实有更好的方法可以做到这一点吗?

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

唯一需要考虑的是垃圾收集何时命中,因为它是不确定的(因此可能永远不会运行)。但你对此无能为力


此外,确保在其中有一个覆盖的
catch(例外e)
子句。GC线程上的异常可能会导致一些奇怪的事情。

有时候,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方法。

这不完全正确。在
MyClass
实例进入终结队列后,可以随时收集信号量实例。因此,在终结器运行之前收集
m_sem
是很有可能的。这实际上只是描述了问题,而不是解决方案(答案)。i、 e.当对象是GC'd时,它仍然不会释放,因此也不会释放信号量。这如何解决在未使用
using
时(或者未调用dispose)确保释放信号量的问题?@PeterRitchie:我假设OP将在其Dispose模式的完整实现中包含
GCHandle
技巧,这将允许在终结器中处置信号量(实际上,当终结器调用
Dispose
)。@JimMischel实际上是个不错的主意:)。我想正如你所说的,RTFM可能更好。经过大量阅读,甚至MSs自己的代码在这件事上似乎也不一致。有时示例从不调用
IDisposable
数据成员上的
Dispose()
,当从终结器调用时,有时会这样做。这一切似乎都是基于MSs自己对所包含的类在幕后所做的事情的了解——在非托管类周围使用一个薄的托管包装器是安全的,否则就不安全了。我想还有另一个讨厌IDisposable的原因:)。你对IDisposable的假设是完全错误的。此接口允许程序员手动释放实例的资源。(例如,建议对占用大量内存的位图进行编译),但调用Dispose()是一个选项。如果您不在代码中调用它,垃圾收集器将在稍后单独释放该实例。所以你写的关于信号量永远不会被释放的说法是错误的。当垃圾收集器处理它时,它将被释放。NET中不再使用的任何东西都将由垃圾收集器收集。但这可能需要一段时间。
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();
      }
   }
}
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();
   }
}