.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 }