我正在尝试使用CountdownEvent仅在事件计数为零时允许线程继续,但是我希望初始计数为零。实际上,我希望返回到零的行为,即只要计数为零,就会发出事件信号,并且只要计数大于零,就会使线程等待。
我可以用 0 初始计数初始化一个 Countdown 事件,但是当我尝试添加到计数时,我得到InvalidOperationException "CountdownEvent_Increment_AlreadyZero" 。
是否有替代类或其他方式可以使用 Countdown 事件来避免此限制?
我正在尝试使用CountdownEvent仅在事件计数为零时允许线程继续,但是我希望初始计数为零。实际上,我希望返回到零的行为,即只要计数为零,就会发出事件信号,并且只要计数大于零,就会使线程等待。
我可以用 0 初始计数初始化一个 Countdown 事件,但是当我尝试添加到计数时,我得到InvalidOperationException "CountdownEvent_Increment_AlreadyZero" 。
是否有替代类或其他方式可以使用 Countdown 事件来避免此限制?
public void Dispatch()
{
using (var ev = new CountdownEvent(1))
{
foreach (var task in <collection_of_tasks_to_start>)
{
ev.AddCount();
// start *task* here. Don't forget to pass *ev* to it!
}
ev.Signal();
ev.Wait();
}
}
// task code
void Handler(CountdownEvent ev)
{
try
{
// do task logic
}
finally
{
ev.Signal();
}
}
为什么以及如何工作?
Dispatch只会正常完成。ev.Signal确保一旦被调用,计数器的ev.Wait初始值不会阻塞执行0Waitev.Wait()调用点的执行。
ev.Wait()这种情况下相当于第 1 点。ev.Wait()柜台不等于0了,因为所有的ev.AddCount()执行。因此,执行将举行。ev.Signal()执行了相应的行),计数器就会下降到0并继续执行退出例程你写了:
我正在执行的操作将创建未知数量的子操作(不是任务或线程)
那么它们是什么?你应该这样做:
倒计时事件;
公共无效 foo() {
ev = new CountdownEvent(1);
foreach(<tasks_to_start 中的任务>){
ev.AddCount();
// 在此处输入代码以启动您的任务
}
ev.Signal();
ev.Wait();
}
公共静态无效youtTask(CountdownEvent ev){
// 一些工作
// ...
// 全部完成后
ev.Signal();
}
所以本质上你需要一个“开/关开关”,而不是一个可以设置任意倒计时的同步对象。CountdownEvent不适合这种情况。
为什么不使用Semaphore初始计数为 1 的 a 呢?
您的问题似乎是一种常见的树步行分叉技术。每次递归时,您都会启动另一个并发操作(将其排入线程池等)。但是您需要等待所有子分支最后完成。只需为您启动的每个子操作的倒计时事件添加 1,并在每个子操作结束时发出信号。只要您安排算法,它就不会发出信号,直到它为每个子操作添加之前,它是安全的。
我应该补充一点,您不需要预先知道计数,只需在根处将其设为 1,每次分叉给孩子时,加 1,然后在每个末尾发出信号,它会动态无需前期成本即可处理任何树木。
CountdownEvent 有一个 Add 方法,可让您增加飞行中的计数。
那有意义吗?我可能偏离了你想要完成的目标。
但是,如果您真的想要一个 CountdownEvent 以您指定的方式运行,那么在一个类中包装几个互锁操作来执行您所说的操作非常容易。
但是,CountdownEvent 是轻量级的,如果没有人在发出信号之前等待,它几乎是免费的。在昂贵的情况下,它是最优的,无论有多少任务(等),它只需要让一个内核转换为信号和一个等待,最坏的情况。
要实现您的建议,需要围绕事件的信令和重置进行同步。倒计时事件依赖于一个简单的原则,只有在 Signal 调用中从非零到零的转换才能发出事件信号。不存在竞争,因为一次不可能有多个线程更改值(它是互锁的),因此只能有一个线程尝试向事件对象发出信号(唤醒另一个等待线程)。完美的。
但是,如果您有多个线程设置和重置它,则需要围绕设置和重置同步,因为计数可能会抖动几次,并且多个线程会同时尝试设置或重置事件。(设置、重置和等待事件都是昂贵的,因为它们都必须进行内核转换并导致上下文切换)。除非您围绕某些东西进行同步以保护设置/重置转换,否则它不会起作用。如果他们将其添加到 CountdownEvent 中,它将不再是最佳的,它会更加昂贵。
这对你有用吗? http://msdn.microsoft.com/en-us/library/dd384749.aspx
编辑
对不起,这是含糊的。使用 SOReader 的答案,您在每个父级中都有一个从 1 开始的倒计时事件 - 然后在子级中使用 TryAddCount 递增,然后将父级递减回 1,然后在子级完成时在父级中从 1 递减到零,最后递减计数父线程的父线程。因此,一系列树状倒计时事件。
我对多线程没有经验,但乍一看这就是我会尝试的。
您可以使用 Queue 对象将“工作”添加到并再次取出。
只有当队列为空时,您才能继续。
但是,是的,我们需要这里的细节......
信号量怎么样:http: //msdn.microsoft.com/en-us/library/system.threading.semaphore.aspx
编辑:以下帖子讨论了为什么不推荐您所描述的内容,并提出了一种解决方法:http ://social.msdn.microsoft.com/Forums/en/parallelextensions/thread/aa49f92c-01a8-4901-9846-91bc1587f3ae
我遇到了同样的问题,但在Barrier.
实际上,如果您考虑一下,CountdownEvent是一个阶段Barrier。
所以为了避免以下限制:
我可以用 0 初始计数初始化一个倒计时事件,但是当我尝试添加到计数时,我得到 InvalidOperationException“CountdownEvent_Increment_AlreadyZero”。
你可以继续Barrier使用它的AddParticipantorRemoveParticipan方法。