.NET反向信号量?

也许现在已经太晚了,但我想不出一个很好的方法来做到这一点。

我已经开始了一堆异步下载,我想等到它们都在程序终止之前完成。 这让我相信我应该在下载开始时增加一些东西,并在它完成时减少它。 但那我怎么等到计数再次为0

信号量以相反的方式工作,因为当没有可用资源时阻塞,而不是当它们全部可用时阻塞(当count为0而不是非零时为块)。

查看本杂志文章中的CountdownLatch课程。

更新:现在由4.0版以后的框架覆盖, CountdownEvent类 。

在.NET 4中,为此目的有一种特殊类型CountdownEvent 。

或者你可以像这样建立类似的东西:

 const int workItemsCount = 10; // Set remaining work items count to initial work items count int remainingWorkItems = workItemsCount; using (var countDownEvent = new ManualResetEvent(false)) { for (int i = 0; i < workItemsCount; i++) { ThreadPool.QueueUserWorkItem(delegate { // Work item body // At the end signal event if (Interlocked.Decrement(ref remainingWorkItems) == 0) countDownEvent.Set(); }); } // Wait for all work items to complete countDownEvent.WaitOne(); } 

看起来像System.Threading.WaitHandle.WaitAll可能非常适合:

等待指定数组中的所有元素接收信号。

那么……你可以抢回主线程上的所有信号量计数器,以便在count为0时阻塞,而不是非零

修订:在这里我假设了三件事:

  • 程序运行时,可以随时启动新的下载作业。
  • 在退出该计划时,将不再需要处理新的下载。
  • 退出程序时,您需要等待所有文件完成下载

所以这是我的解决方案,修改过:

使用足够大的计数器初始化信号量,这样您就不会达到最大值(根据您的情况,它可能只是100或10):

 var maxDownloads = 1000; _semaphore = new Semaphore(0, maxDownloads); 

然后在每次下载时,从WaitOne()开始,然后开始下载,以便在程序退出时,不会开始下载。

 if (_semaphore.WaitOne()) /* proceeds with downloads */ else /* we're terminating */ 

然后在下载完成时,释放一个计数器(如果我们已经获得一个):

 finally { _semaphore.Release(1); } 

然后在“退出”事件中, 消耗信号量上的所有计数器

 for (var i = 0; i < maxDownloads; i++) _semaphore.WaitOne(); // all downloads are finished by this point. 

...

我有一个类似的问题,我需要在某些事件时重置服务器,但必须等待所有打开的请求完成后再杀死它。

我在服务器启动时使用CountdownEvent类用1初始化它,并在每个请求中执行:

 try { counter.AddCount(); //do request stuff } finally { counter.Signal(); } 

收到ResetEvent后,我会向计数器发出一次信号以消除起始1,并等待实时请求发出信号。

 void OnResetEvent() { counter.Signal(); counter.Wait(); ResetServer(); //counter.Reset(); //if you want to reset everything again. } 

基本上你用一个初始化CountdownEvent,以便它处于非信号状态,并且每次AddCount调用你都在增加计数器,并且每次Signal调用你都在减少它,总是保持在1.在你的等待线程中你首先发出信号它一次将初始1值减小为0,如果没有线程运行,Wail()将立即停止阻塞,但如果还有其他线程仍在运行,则等待线程将等待直到它们发出信号。 注意,一旦计数器达到0,所有后续的AddCount调用都将抛出exception,您需要先重置计数器。

对于每个线程,您启动Interlock.Increment一个计数器。 并且对于线程完成的每个回调,递减它。

然后用Thread.Sleep(10)或其他东西做一个循环,直到计数达到零。

这是CountdownLatch的C#2.0实现:

 public class CountdownLatch { private int m_count; private EventWaitHandle m_waitHandle = new EventWaitHandle(true, EventResetMode.ManualReset); public CountdownLatch() { } public void Increment() { int count = Interlocked.Increment(ref m_count); if (count == 1) { m_waitHandle.Reset(); } } public void Add(int value) { int count = Interlocked.Add(ref m_count, value); if (count == value) { m_waitHandle.Reset(); } } public void Decrement() { int count = Interlocked.Decrement(ref m_count); if (m_count == 0) { m_waitHandle.Set(); } else if (count < 0) { throw new InvalidOperationException("Count must be greater than or equal to 0"); } } public void WaitUntilZero() { m_waitHandle.WaitOne(); } } 

基于这里的建议,这就是我想出的。 除了等待计数为0之外,如果产生太multithreading(count> max),它将会hibernate。 警告:这尚未经过全面测试。

 public class ThreadCounter { #region Variables private int currentCount, maxCount; private ManualResetEvent eqZeroEvent; private object instanceLock = new object(); #endregion #region Properties public int CurrentCount { get { return currentCount; } set { lock (instanceLock) { currentCount = value; AdjustZeroEvent(); AdjustMaxEvent(); } } } public int MaxCount { get { return maxCount; } set { lock (instanceLock) { maxCount = value; AdjustMaxEvent(); } } } #endregion #region Constructors public ThreadCounter() : this(0) { } public ThreadCounter(int initialCount) : this(initialCount, int.MaxValue) { } public ThreadCounter(int initialCount, int maximumCount) { currentCount = initialCount; maxCount = maximumCount; eqZeroEvent = currentCount == 0 ? new ManualResetEvent(true) : new ManualResetEvent(false); } #endregion #region Public Methods public void Increment() { ++CurrentCount; } public void Decrement() { --CurrentCount; } public void WaitUntilZero() { eqZeroEvent.WaitOne(); } #endregion #region Private Methods private void AdjustZeroEvent() { if (currentCount == 0) eqZeroEvent.Set(); else eqZeroEvent.Reset(); } private void AdjustMaxEvent() { if (currentCount <= maxCount) Monitor.Pulse(instanceLock); else do { Monitor.Wait(instanceLock); } while (currentCount > maxCount); } #endregion }