急切地处理ManualResetEvent

时间:2011-06-07 14:17:44

标签: c# .net thread-safety

我有一个类允许其他线程等到它使用ManualResetEventSlim完成操作。 (操作通常很简短)

这个类没有明确的生命周期,所以我没有一个地方可以轻易地关闭事件 相反,我希望一旦它完成就关闭事件 - 一旦发出信号,并且在任何等待的线程唤醒之后。

出于性能原因,我宁愿不使用锁。

这段代码是否是线程安全的,是否可以更快?

volatile bool isCompleted;
volatile int waitingCount;
ManualResetEventSlim waiter = new ManualResetEventSlim();

//This method is called on any thread other than the one that calls OnCompleted
public void WaitForCompletion() {
    if (isCompleted)
        return;

    Interlocked.Increment(ref waitingCount);
    Thread.MemoryBarrier();
    if (!isCompleted)
        waiter.Wait();

    if (0 == Interlocked.Decrement(ref waitingCount)) {
        waiter.Dispose();
        waiter = null;
    }
    return;
}

//This method is called exactly once.
protected internal virtual void OnCompleted(string result) {
    Result = result;
    isCompleted = true;
    Thread.MemoryBarrier();
    if (waitingCount == 0) {
        waiter.Dispose();
        waiter = null;
    } else
        waiter.Set();
}

2 个答案:

答案 0 :(得分:1)

更糟糕的是,在某些情况下,它根本不会处理服务员。如果您在OnCompleted时致电waitingCount > 0,则isCompleted标记将设置为true,但不会处理服务员。当某些内容调用WaitForCompletion时,它会看到isCompletedtrue,并立即退出。 waiter.Dispose永远不会被调用。

为什么不使用SpinLock之类的东西,它使用与ManualResetEventSlim相同的逻辑?如果您的等待时间通常非常短,那么锁定可能不会引起争议,这是一个巨大的胜利。如果等待时间很长,那么ManualResetEventSlim无论如何都要付出内核转换的代价。

您是否确定使用锁定会非常昂贵?有“知道”,然后有测量。 。

答案 1 :(得分:1)

我在代码中看到的最重要的事情是在调用waiter后设置nullDispose。我负责管理非托管接口的大量托管包装,当我转移到.Net 4.0时,这种做法在某些线程场景中回过头来咬我。

ManualResetEventSlim.Dispose上的MSDN信息表明它不是线程安全的,但是,查看其实际实现,从多个线程多次调用Dispose没有任何危险。此外,IDisposable的实现应该非常容忍多个调用(在其设计指南中指定)。

我玩过的一个想法会稍微重新排序OnCompleted以允许读者在完成后不久订阅:

//This method is called exactly once.
protected internal virtual void OnCompleted(string result) {
    Result = result;
    isCompleted = true;

    waiter.Set();
    Thread.MemoryBarrier();
    if (waitingCount == 0) {
        waiter.Dispose();
    }
}

<击>