Monitor.TryEnter не работает
Часть моего кода:
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
никогда не выполняется, почему?
Ответы
Ответ 1
Смешивание 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);
}
Обратите внимание, что вы также можете подумать, что произойдет, если произошла ошибка и т.д.; как значение становится reset? Вы можете использовать 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);
}
Ответ 2
Вы не можете использовать нить-аффинный тип типа Monitor
с await
. В этом конкретном случае вы всегда получаете блокировку в одном потоке (поток пользовательского интерфейса), и этот тип блокировки позволяет рекурсивную блокировку.
Попробуйте вместо 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();
}
Ответ 3
Вы не можете использовать await
между вызовами метода Monitor.TryEnter()
и Monitor.Exit()
. После await
контекст потока может быть другим, что означало бы, что поток не имеет блокировки entered
, и поэтому он не сможет exit
его.
Фактически, компилятор защитит вас, если вы используете ключевое слово lock
:
lock(_sync)
{
await Task.Delay(...); // <- Compiler error...
}
Ответ 4
Что происходит, так это то, что TryEnter
будет успешным, если текущий поток уже получил блокировку. Событие KeyDown
всегда будет запускаться в потоке диспетчера, в то время как фоновый поток обрабатывает ожидание, а затем помещает разблокировку обратно в поток диспетчера.
Ответ 5
TryEnter
будет работать в вашем потоке gui. Действительно, чтобы поток мог получать монитор несколько раз без блокировки, ему просто нужно выпустить их столько же раз.
Ваш вызов Monitor.Exit
будет выполняться в контексте, продиктованном вашим вызовом async
. Если он заканчивается в потоке, отличном от потока, который называется TryEnter
, тогда он не сможет выпустить монитор.
Итак, вы каждый раз приобретаете монитор в одном и том же потоке, который никогда не будет блокироваться, и вы выпускаете его на какой-то другой поток, который может работать. Вот почему вы можете быстро щелкнуть в 5-секундном окне.