AutoResetEvent - 两个快速调用集不保证线程释放 - 为什么?

时间:2012-02-29 04:42:23

标签: c# warnings msdn autoresetevent

我正在MSDN上阅读AutoResetEvent文档,并且警告有点困扰我......

“重要: 无法保证每次调用Set方法都会释放一个线程。如果两个调用太靠近,那么第二次调用在线程释放之前发生,则只释放一个线程。就好像第二次通话没有发生一样。此外,如果在没有线程等待并且已经发出AutoResetEvent信号的情况下调用Set,则该调用无效。“

但是这个警告基本上杀死了拥有这种线程同步技术的原因。例如,我有一个列表,可以保留工作。并且只有一个生产者将添加作业到列表中。我有消费者(不止一个),等着从列表中得到这份工作......就像这样..

制片:

void AddJob(Job j)
{
    lock(qLock)
    {
        jobQ.Enqueue(j);
    }

    newJobEvent.Set(); // newJobEvent is AutoResetEvent
}

消费

void Run()
{
    while(canRun)
    {
        newJobEvent.WaitOne();

        IJob job = null;

        lock(qLock)
        {
            job = jobQ.Dequeue();
        }

        // process job
    }
}

如果上述警告为真,那么如果我很快将两个作业排队,那么只有一个线程会接受这个工作,不是吗?我假设Set将是原子的,即它执行以下操作:

  1. 设置活动
  2. 如果线程正在等待,请选择一个线程来唤醒
  3. 重置活动
  4. 运行选定的主题。
  5. 所以我基本上对MSDN中的警告感到困惑。这是一个有效的警告吗?

2 个答案:

答案 0 :(得分:2)

即使警告不正确且Set为原子,为什么要在这里使用AutoResetEvent?假设您有一些生产者排队等待3个事件,并且有一个消费者。在处理完第二个作业之后,消费者会阻止并且永远不会处理第三个作业。

我会使用ReaderWriterLockSlim进行此类同步。基本上,您需要多个生产者才能拥有写入锁,但是您不希望消费者在只读取队列大小的情况下长时间锁定生产者。

答案 1 :(得分:2)

MSDN上的消息确实是有效的消息。内部发生的事情是这样的:

  1. 线程A等待事件
  2. 主题B设置事件
  3. [如果线程A在自旋锁中]
    1. [yes]线程a检测到事件已设置,取消设置并恢复其工作
    2. [no]该事件将告诉线程A唤醒,一旦被唤醒,线程A将取消该事件恢复其工作。
  4. 请注意,内部逻辑不同步,因为线程B不等待线程A继续其业务。你可以通过引入一个临时的ManualResetEvent使这个同步,线程A必须在它继续工作并且线程B必须等待时发出信号。由于Windows线程模型的内部工作,默认情况下不会这样做。我想文档是误导性的,但正确的说Set方法只释放一个或多个等待线程。

    或者我建议你看一下.NET 4.0中引入的BCL的System.Collections.Concurrent命名空间中的BlockingCollection类,它正是你想要做的事情