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
lock
is reentrant, while theSemaphoreSlim
is not. So programming with thelock
is more forgiving. In case there is a rare path in your app where you are acquiring the same lock twice, thelock
will acquire it successfully, while theSemaphoreSlim
will deadlock.The
lock
is syntactic sugar around theMonitor
class. In other words there is language support for theMonitor
in C#, and there isn't for theSemaphoreSlim
. So using thelock
is 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.LockContentionCount
property: "Gets the number of times there was contention when trying to take the monitor's lock." There are no statistics available for theSemaphoreSlim
class.The
SemaphoreSlim
isIDisposable
, 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
lock
can 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.