我的代码隐藏的一部分:
object _sync = new object();
private async void OnKeyDown(object sender, KeyEventArgs e) {
if (!Monitor.TryEnter(_sync)) return;
Trace.Write("taken...");
await Task.Delay(TimeSpan.FromSeconds(5));
Trace.WriteLine(" done");
Monitor.Exit(_sync);
}
输出(在不到5秒的时间内按下几次):
taken...taken...taken... done
done
done
如何先得??永远不会采用_sync
锁,为什么?
答案 0 :(得分:14)
混合Monitor
和await
......有点风险。看起来你要做的就是确保它一次只运行一次。我怀疑Interlocked
可能更简单:
object _sync = new object();
int running = 0;
private async void OnKeyDown(object sender, KeyEventArgs e) {
if(Interlocked.CompareExchange(ref running, 1, 0) != 0) return;
Trace.Write("taken...");
await Task.Delay(TimeSpan.FromSeconds(5));
Trace.WriteLine(" done");
Interlocked.Exchange(ref running, 0);
}
注意您可能还想想如果发生错误等会发生什么;该值如何重置?您可以使用try
/ finally
:
if(Interlocked.CompareExchange(ref running, 1, 0) != 0) return;
try {
Trace.Write("taken...");
await Task.Delay(TimeSpan.FromSeconds(5));
Trace.WriteLine(" done");
} finally {
Interlocked.Exchange(ref running, 0);
}
答案 1 :(得分:12)
您不能将Monitor
与await
一起使用线程仿射类型。在这种特殊情况下,你总是在同一个线程(UI线程)上获取锁,这种类型的锁允许递归锁定。
请改为SemaphoreSlim
(WaitAsync
和Release
而不是Enter
和Exit
):
SemaphoreSlim _sync = new SemaphoreSlim(1);
private async void OnKeyDown(object sender, KeyEventArgs e) {
await _sync.WaitAsync();
Trace.Write("taken...");
await Task.Delay(TimeSpan.FromSeconds(5));
Trace.WriteLine(" done");
_sync.Release();
}
答案 2 :(得分:6)
您无法在await
和Monitor.TryEnter()
方法调用之间使用Monitor.Exit()
。在await
之后,线程上下文可能不同,这意味着线程不会有entered
锁,因此它不能exit
它。
事实上,如果您使用lock
关键字,编译器会保护您:
lock(_sync)
{
await Task.Delay(...); // <- Compiler error...
}
答案 3 :(得分:2)
如果当前主题已获得锁定,TryEnter
会成功。 KeyDown
事件将始终在Dispatcher线程上触发,而后台线程正在处理等待,然后在解调器线程上将解锁队列排队。
答案 4 :(得分:2)
TryEnter
将在你的gui线程上运行。它对于一个线程有效地多次获取监视器而不会阻塞是有效的,它只需要释放它们相同的次数。
您对Monitor.Exit
的来电将在async
来电指示的上下文中运行。如果它最终在调用TryEnter
的线程以外的线程上运行,那么它将无法释放监视器。
所以,你每次都在同一个线程上获取监视器,它永远不会阻塞,你可以在其他一些线程上释放它,这可能有用。这就是为什么你能够在5秒窗口内快速点击。