我有一个类允许其他线程等到它使用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();
}
答案 0 :(得分:1)
更糟糕的是,在某些情况下,它根本不会处理服务员。如果您在 OnCompleted
时致电waitingCount > 0
,则isCompleted
标记将设置为true
,但不会处理服务员。当某些内容调用WaitForCompletion
时,它会看到isCompleted
为true
,并立即退出。 waiter.Dispose
永远不会被调用。
为什么不使用SpinLock之类的东西,它使用与ManualResetEventSlim
相同的逻辑?如果您的等待时间通常非常短,那么锁定可能不会引起争议,这是一个巨大的胜利。如果等待时间很长,那么ManualResetEventSlim
无论如何都要付出内核转换的代价。
您是否确定使用锁定会非常昂贵?有“知道”,然后有测量。 。
答案 1 :(得分:1)
我在代码中看到的最重要的事情是在调用waiter
后设置null
到Dispose
。我负责管理非托管接口的大量托管包装,当我转移到.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();
}
}
击> <击> 撞击>