Skip to main content

So now, that we know what a Semaphore is we may go ahead and replace the lock with a Semaphore. In our case, the SemaphoreSlim class is the ideal data type to use since we will be using it in a single process.

// Replacing the Lock with a Semaphore
//
// Article: // https://blog.cdemi.io/async-waiting-inside-c-sharp-locks/
//
// So now, that we know what a Semaphore is we may go ahead and replace the lock
// with a Semaphore. In our case, the SemaphoreSlim class is the ideal data type
// to use since we will be using it in a single process.
//
// It is vital to always release the Semaphore when you are ready, this is why
// it is suggested to be placed inside a try...finally clause[3].
//
// Calling WaitAsync on the semaphore produces a task that will be completed
// when that thread has been granted access to the Semaphore.

// Instantiate a Singleton of the Semaphore with a value of 1.
// This means that only 1 thread can be granted access at a time.
static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);

// Asynchronously wait to enter the Semaphore. If no-one has been granted access
// to the Semaphore, code execution will proceed, otherwise this thread waits
// here until the semaphore is released
await semaphoreSlim.WaitAsync();
try
{
    await Task.Delay(1000);
}
finally
{
    // When the task is ready, release the semaphore. It is vital to ALWAYS
    // release the semaphore when we are ready, or else we will end up with a
    // Semaphore that is forever locked.
    //
    // This is why it is important to do the Release within a try...finally
    // clause; program execution may crash or take a different path, this way
    // you are guaranteed execution
    semaphoreSlim.Release();
}

// ---------------------------------------------------------------------
// Example 2
// ---------------------------------------------------------------------
//
// https://davidsekar.com/c-sharp/dont-lock-on-async-tasks
// System.Threading assembly comes with Semaphore and SemaphoreSlim that helps
// you to control concurrent access to the shared resource.

public class LockTest
{
    private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);

    public async Task RecordSuccess(int batchId)
    {
        await _lock.WaitAsync();
        try
        {
            // Record a success in database
            var success = GetCurrentSuccessCountFromDB();
            SaveSuccessCountToDB(success + 1);
        }
        finally
        {
            _lock.Release();
        }
    }

    public async Task RecordFailure(int batchId)
    {
        await _lock.WaitAsync();
        try
        {
            // Record a failure in database
            var success = GetCurrentFailureCountFromDB();
            SaveFailureCountToDB(success + 1);
        }
        finally
        {
            _lock.Release();
        }
    }
}