Я опаздываю на вечеринку, но недавно узнала о SemaphoreSlim:
Раньше я использовал lock для синхронной блокировки и логическое значение busy для асинхронной блокировки. Теперь я просто использую SemaphoreSlim для всего.
private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
private void DoStuff()
{
semaphoreSlim.Wait();
try
{
DoBlockingStuff();
}
finally
{
semaphoreSlim.Release();
}
}
Против
private object locker = new object();
private void DoStuff()
{
lock(locker)
{
DoBlockingStuff();
}
}
Есть ли какие-либо синхронные случаи, когда я должен предпочесть использовать lock вместо SemaphoreSlim? Если так, то кто они?
Вот несколько преимуществ lock перед SemaphoreSlim:
Lock является реентерабельным, а SemaphoreSlim — нет. Так что программирование с помощью lock более снисходительно. В случае, если в вашем приложении есть редкий путь, где вы дважды получаете одну и ту же блокировку, lock получит ее успешно, а SemaphoreSlim заблокирует.
Lock — это синтаксический сахар класса Monitor. Другими словами, для Monitor в C# есть языковая поддержка, а для SemaphoreSlim нет. Таким образом, использование lock сравнительно более удобно и менее многословно.
Вы можете написать более надежный код с помощью lock, потому что вы можете добавить отладочные утверждения во вспомогательные методы, что блокировка была получена: Debug.Assert(Monitor.IsEntered(_locker));
Статистику конфликтов можно получить с помощью свойства Monitor.LockContentionCount: «Получает, сколько раз возникали конфликты при попытке получить блокировку монитора». Статистики по классу SemaphoreSlim нет.
SemaphoreSlim есть IDisposable, поэтому вам нужно подумать о том, когда (и нужно ли) его утилизировать. Сможете ли вы уйти, не выбросив его? Вы выбрасываете его преждевременно и рискуете ObjectDisposedException? Это вопросы, на которые вам не нужно отвечать с помощью lock.
Lock может выжить в сценарии прерванного потока. Он переводится компилятором C# так:
bool lockTaken = false;
try
{
Monitor.Enter(obj, ref lockTaken);
DoBlockingStuff();
}
finally
{
if (lockTaken)
{
Monitor.Exit(obj);
}
}
Monitor.Enter был тщательно закодирован, поэтому в случае прерывания потока lockTaken будет иметь правильное значение. Напротив, SemaphoreSlim.Wait вызывается за пределами блока try/finally, поэтому есть небольшое окно, в котором текущий поток может быть прерван без снятия блокировки, что приводит к взаимоблокировке.
Платформа .NET прекратила поддержку метода Thread.Abort, так что можно с полным правом сказать, что последний пункт имеет только теоретическое значение.