13

我正在尝试使用CountdownEvent仅在事件计数为零时允许线程继续,但是我希望初始计数为零。实际上,我希望返回到零的行为,即只要计数为零,就会发出事件信号,并且只要计数大于零,就会使线程等待。

我可以用 0 初始计数初始化一个 Countdown 事件,但是当我尝试添加到计数时,我得到InvalidOperationException "CountdownEvent_Increment_AlreadyZero" 。

是否有替代类或其他方式可以使用 Countdown 事件来避免此限制?

4

8 回答 8

7

编辑

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();
    }
}

为什么以及如何工作?

  1. 如果你还没有产生任何任务
    • 什么都不会发生,Dispatch只会正常完成。
    • ev.Signal确保一旦被调用,计数器的ev.Wait初始值不会阻塞执行0Wait
  2. 如果您产生了至少一项任务
    • 我们有两种情况需要考虑:快速和慢速运行的任务,并了解它们如何影响ev.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();
}
于 2011-01-03T01:32:13.143 回答
3

如果您可以使用 .NET 4.0 或.NET 3.5 的响应式扩展(它具有 .NET 4 TPL 功能的反向移植),您可以查看Barrier类。它允许您协调多个并行任务,以便在屏障中的所有参与者都发出信号到达之前,它们不会继续。它还应该满足您在处理过程中让参与者出现和消失的要求。

于 2010-12-08T18:48:30.763 回答
2

所以本质上你需要一个“开/关开关”,而不是一个可以设置任意倒计时的同步对象。CountdownEvent不适合这种情况。

为什么不使用Semaphore初始计数为 1 的 a 呢?

于 2010-12-08T13:50:07.840 回答
1

您的问题似乎是一种常见的树步行分叉技术。每次递归时,您都会启动另一个并发操作(将其排入线程池等)。但是您需要等待所有子分支最后完成。只需为您启动的每个子操作的倒计时事件添加 1,并在每个子操作结束时发出信号。只要您安排算法,它就不会发出信号,直到它为每个子操作添加之前,它是安全的。

我应该补充一点,您不需要预先知道计数,只需在根处将其设为 1,每次分叉给孩子时,加 1,然后在每个末尾发出信号,它会动态无需前期成本即可处理任何树木。

CountdownEvent 有一个 Add 方法,可让您增加飞行中的计数。

那有意义吗?我可能偏离了你想要完成的目标。

但是,如果您真的想要一个 CountdownEvent 以您指定的方式运行,那么在一个类中包装几个互锁操作来执行您所说的操作非常容易。

但是,CountdownEvent 是轻量级的,如果没有人在发出信号之前等待,它几乎是免费的。在昂贵的情况下,它是最优的,无论有多少任务(等),它只需要让一个内核转换为信号和一个等待,最坏的情况。

要实现您的建议,需要围绕事件的信令和重置进行同步。倒计时事件依赖于一个简单的原则,只有在 Signal 调用中从非零到零的转换才能发出事件信号。不存在竞争,因为一次不可能有多个线程更改值(它是互锁的),因此只能有一个线程尝试向事件对象发出信号(唤醒另一个等待线程)。完美的。

但是,如果您有多个线程设置和重置它,则需要围绕设置和重置同步,因为计数可能会抖动几次,并且多个线程会同时尝试设置或重置事件。(设置、重置和等待事件都是昂贵的,因为它们都必须进行内核转换并导致上下文切换)。除非您围绕某些东西进行同步以保护设置/重置转换,否则它不会起作用。如果他们将其添加到 CountdownEvent 中,它将不再是最佳的,它会更加昂贵。

于 2012-12-18T03:01:34.960 回答
1

这对你有用吗? http://msdn.microsoft.com/en-us/library/dd384749.aspx

编辑
对不起,这是含糊的。使用 SOReader 的答案,您在每个父级中都有一个从 1 开始的倒计时事件 - 然后在子级中使用 TryAddCount 递增,然后将父级递减回 1,然后在子级完成时在父级中从 1 递减到零,最后递减计数父线程的父线程。因此,一系列树状倒计时事件。

我对多线程没有经验,但乍一看这就是我会尝试的。

于 2011-04-15T20:14:55.417 回答
0

您可以使用 Queue 对象将“工作”添加到并再次取出。

只有当队列为空时,您才能继续。

但是,是的,我们需要这里的细节......

于 2011-03-15T11:28:08.540 回答
0

信号量怎么样: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

于 2010-12-08T13:47:58.700 回答
0

我遇到了同样的问题,但在Barrier.

实际上,如果您考虑一下,CountdownEvent是一个阶段Barrier

所以为了避免以下限制:

我可以用 0 初始计数初始化一个倒计时事件,但是当我尝试添加到计数时,我得到 InvalidOperationException“CountdownEvent_Increment_AlreadyZero”。

你可以继续Barrier使用它的AddParticipantorRemoveParticipan方法。

于 2018-10-03T06:26:14.593 回答