多个线程在等待一个事件?

时间:2011-03-24 07:58:45

标签: c# multithreading synchronization

我想要的是AutoResetEvent的等价,多个线程可以等待,所有这些都在设置时恢复。

我知道这可以通过每个线程一个AutoResetEvent并设置每个线程来实现 - 但是有更简单的方法吗?一种不依赖于事件句柄数组的方法吗?

有效的(我认为)我希望能够做到这一点:

private volatile string state;
private MultiEventHandle stateChanged = new MultiEventHandle();

public void WaitForBlob()
{
  while (true)
  {
    object saved = stateChanged.Current;  // some sentinel value
    if (state == "Blob") break;
    stateChanged.WaitTilNot(saved);  // wait til sentinel value != "current"
  }
}

public void SetBlob()
{
  state = "Blob";
  stateChanged.Change();  // stateChanged.Current becomes a new sentinel object
}

即,任意数量的线程都可以调用WaitForBlob,并且在任何时候(没有竞争条件)SetBlob都可以被另一个线程调用,并且所有等待的线程将立即检测到更改 - 并且重要的是,没有自旋锁或Threading.Sleeps。

现在我想我可以相对容易地实现“MultiEventHandle”。但我的问题是......有更好的方法吗?当然,我认为这是错误的,因为它必须是一个非常常见的用例,但我似乎无法找到一个内置的工具。我担心我可能会在这里发明方形轮..

3 个答案:

答案 0 :(得分:3)

我已经使用Monitor.PulseAll / Wait在幕后编写了一个可能的解决方案到了一个“WatchedVariable”类中(在这个过程中学习了一下Monitor类)。如果其他任何人遇到同样的问题,这里发布 - 可能与不可变数据结构有关。感谢Jon Skeet的帮助。

用法:

private WatchedVariable<string> state;

public void WaitForBlob()
{
  string value = state.Value;
  while (value != "Blob")
  {
    value = state.WaitForChange(value);
  }
}

实现:

public class WatchedVariable<T>
    where T : class
{
    private volatile T value;
    private object valueLock = new object();

    public T Value
    {
        get { return value; }
        set
        {
            lock (valueLock)
            {
                this.value = value;
                Monitor.PulseAll(valueLock);  // all waiting threads will resume once we release valueLock
            }
        }
    }

    public T WaitForChange(T fromValue)
    {
        lock (valueLock)
        {
            while (true)
            {
                T nextValue = value;
                if (nextValue != fromValue) return nextValue;  // no race condition here: PulseAll can only be reached once we hit Wait()
                Monitor.Wait(valueLock);  // wait for a changed pulse
            }
        }
    }

    public WatchedVariable(T initValue)
    {
        value = initValue;
    }
}

虽然它通过了我的测试用例,但使用风险自负。

现在咨询meta以找出我应该接受的答案..

答案 1 :(得分:2)

有什么理由不使用ManualResetEvent

,当一个等待的线程已经过去时,它不会重置自己

当然,这意味着如果在所有等待线程经过之后你需要Reset事件,你需要一些方法来检测它。您可以可能使用Semaphore代替,但我怀疑它会很复杂。

在您设置之后,您是否需要重置事件immediatley?

答案 2 :(得分:1)

我想出了一个不同的解决方案来解决这个问题。 它假设等待事件的线程不是WaitOne()调用的紧密循环,并且在等待调用之间有一些工作。 它使用一个AutoResetEvent,连续调用WaitOne(0)Set(),直到没有其他线程等待该事件。

// the only event we'll use:
AutoResetEvent are = new AutoResetEvent(false);
// starting threads:
for (int i = 0; i < 10; i++)
{
    string name = "T" + i; 
    new Thread(() => { while (true) { are.WaitOne(); WriteLine(name); } }).Start();
}

// release all threads and continue:
while (!are.WaitOne(0))
    are.Set();

上面的代码测试了1000个线程,它确实全部释放了它们(尽管在while循环上有太多迭代的开销,当在等待调用之间有更多工作时,可以很容易地将其限制为零)线程。

文档中我不清楚的一点是,Set()是否有可能释放稍后在同一线程上调用的WaitOne() - 如果这种情况可能,则此解决方案不安全使用因为在退出while循环之前它可能不会释放所有线程。 如果有人能够对它有所了解,那就太好了。