也许是太晚了,但我想不出一个好的方法来做到这一点。
我已经开始了一堆异步下载,我想等到它们全部完成后再程序终止。这让我相信我应该在下载开始时增加一些东西,并在下载完成时减少它。但是,我该如何等到计数再次为 0?
信号量以相反的方式工作,因为当没有可用资源时阻塞,而不是在它们都可用时阻塞(当计数为 0 时阻塞,而不是非零)。
也许是太晚了,但我想不出一个好的方法来做到这一点。
我已经开始了一堆异步下载,我想等到它们全部完成后再程序终止。这让我相信我应该在下载开始时增加一些东西,并在下载完成时减少它。但是,我该如何等到计数再次为 0?
信号量以相反的方式工作,因为当没有可用资源时阻塞,而不是在它们都可用时阻塞(当计数为 0 时阻塞,而不是非零)。
更新:自 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 而不是 non-zero 时阻塞。
修订:在这里我假设了 3 件事:
所以这是我的解决方案,已修改:
使用足够大的计数器初始化信号量,这样您就不会达到最大值(根据您的情况,它可能只是 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); }
然后在“退出”事件中,消耗掉 Semaphore 上的所有计数器:
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 调用都会抛出异常,您需要先重置计数器。
对于每个线程,您启动 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 之外,如果您产生太多线程(计数 > 最大值),它会休眠。警告:这没有经过全面测试。
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
}