【问题标题】:Return to zero CountdownEvent归零倒计时事件
【发布时间】:2010-12-08 13:39:40
【问题描述】:

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

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

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

【问题讨论】:

  • 你解决过这个问题吗?你是怎么做到的?

标签: c# .net multithreading countdownevent


【解决方案1】:

编辑

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(){ ev.AddCount(); // 在此处输入代码以启动您的任务 } ev.Signal(); ev.Wait(); } 公共静态无效youtTask(CountdownEvent ev){ // 一些工作 // ... // 全部完成后 ev.Signal(); }

【讨论】:

  • 所以您使用额外的倒计时 1 表示仍在添加任务?
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    您为什么不直接使用初始计数为 1 的 Semaphore

    【讨论】:

    • 不完全,重要的是我有这种计数行为。实际上,我正在执行一个将创建未知数量的子操作(不是任务或线程)的操作,当每个操作开始时我收到一个信号,当每个操作结束时我收到一个信号。在所有子操作完成之前,我不想继续。 (仅供参考,我正在围绕本机 dll 编写一个相当棘手的绑定,因此有不寻常的要求)
    • (数量未知的)操作是一次性创建的,还是有可能“分批”创建?换句话说,你能在某个时候说“好的,我创建了其中的 X 个,并且我知道在这些 X 全部完成之前我不会再创建”?
    【解决方案4】:

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

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

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

    【讨论】:

      【解决方案5】:

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

      我应该补充一点,您不需要预先知道计数,只需在根处将其设为 1,每次分叉给孩子时,加 1,然后在每一棵树的结尾,它将动态处理任何树,而无需前期成本。

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

      这有意义吗?我可能不知道您要完成的工作。

      但是,如果您真的想要一个 CountdownEvent 以您指定的方式运行,则可以很容易地将几个互锁操作包装在一个类中以按照您所说的方式进行操作。

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

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 你能举个例子来说明如何使用它们吗?我考虑过它们,但无法避免需要知道最大计数。
        • 你能发布你的非工作代码,以便我更好地理解你想要完成的事情吗?
        【解决方案7】:

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

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

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

        【讨论】:

          【解决方案8】:

          我遇到了同样的issue,但在Barrier的上下文中。

          其实仔细想想,CountdownEvent 是一个单相Barrier

          所以为了避免以下限制:

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

          您可以继续使用Barrier 并使用其AddParticipantRemoveParticipan 方法。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-12-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-04-08
            相关资源
            最近更新 更多