I'm late to the party, but I recently learned about SemaphoreSlim:
I used to use lock for synchronous locking, and a busy boolean for asynchronous locking. Now I just use SemaphoreSlim for everything.
private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
private void DoStuff()
{
semaphoreSlim.Wait();
try
{
DoBlockingStuff();
}
finally
{
semaphoreSlim.Release();
}
}
vs
private object locker = new object();
private void DoStuff()
{
lock(locker)
{
DoBlockingStuff();
}
}
Are there any synchronous cases where I should prefer using lock over SemaphoreSlim? If so, what are they?
CodePudding user response:
Here are a few advantages of the lock over the SemaphoreSlim:
The
lockis reentrant, while theSemaphoreSlimis not. So programming with thelockis more forgiving. In case there is a rare path in your app where you are acquiring the same lock twice, thelockwill acquire it successfully, while theSemaphoreSlimwill deadlock.The
lockis syntactic sugar around theMonitorclass. In other words there is language support for theMonitorin C#, and there isn't for theSemaphoreSlim. So using thelockis comparatively more convenient and less verbose.You can write more robust code with the
lock, because you can add debugging assertions in auxiliary methods that the lock has been acquired:Debug.Assert(Monitor.IsEntered(_locker));You can get contention statistics with the
Monitor.LockContentionCountproperty: "Gets the number of times there was contention when trying to take the monitor's lock." There are no statistics available for theSemaphoreSlimclass.The
SemaphoreSlimisIDisposable, so you have to think about when (and whether) to dispose it. Can you get away without disposing it? Are you disposing it prematurely and risk anObjectDisposedException? These are questions that you don't have to answer with thelock.The
lockcan survive in the scenario of an aborted thread. It is translated by the C# compiler like this:
bool lockTaken = false;
try
{
Monitor.Enter(obj, ref lockTaken);
DoBlockingStuff();
}
finally
{
if (lockTaken)
{
Monitor.Exit(obj);
}
}
The Monitor.Enter has been coded carefully so that in case the thread is aborted, the lockTaken will have the correct value. On the contrary the SemaphoreSlim.Wait is called outside of the try/finally block, so there is a small window that the current thread can be aborted without releasing the lock, resulting in a deadlock.
The .NET platform has dropped support for the Thread.Abort method, so you could rightfully say that the last point has only theoretical value.
