So I'm working on the Web API for my website and certain API calls need to be performed with thread safety in the application's runtime. I have created a locking service which uses a semaphore for locking. The locking service has been declared as a singleton dependency. This locking service is to be used within my API controller with the following flow:
controller waits to obtain lock -> controller obtains lock -> perform thread sensitive process -> release lock
Below is the code for my locking service:
public class LockingService : ILockingService
{
private SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public LockingService() { }
public async Task<bool> WaitForLock()
{
bool result = false;
await _semaphore.WaitAsync();
result = true;
return result;
}
public void ReleaseLock()
{
_semaphore.Release(1);
}
}
Program.cs:
builder.Services.AddSingleton<ILockingService,LockingService>();
Usage in controller:
public async Task MyMethod()
{
await _lockingService.WaitForLock();
// Perform thread sensitive process
_lockingService.ReleaseLock();
}
From what I currently understand, singleton dependencies are shared as a single instance throughout the application's lifetime. Am I correct to infer that this implementation is thread safe?
Best Answer
This is thread safe, since the controllers always get the same instance of the
LockingService
.While such a "globally" accessible service is not exactly clean, it can be a pragmatic solution. However, the current code is error-prone, because other services must not forget to call
ReleaseLock
in all cases (especially exceptions). It can help to replace theWaitForLock
andReleaseLock
with something likeRunWithLock
that takes a callback:This guarantees that the semaphore will always be released when the callback is finished.
Usage:
If you have operations that need to compute a result while holding the lock, you can add a second overload:
Usage: