I have my own class AsyncLock.
I made it IDisposable, so it can use a using-block.
(the unlock of the semaphore in the idispose ensures it will also be unlocked if there is an exception, without the need for try-finally)
namespace Test { public abstract class AsyncLock : System.IAsyncDisposable { private readonly System.Threading.SemaphoreSlim m_semaphore; protected AsyncLock(System.Threading.SemaphoreSlim semaphore) { this.m_semaphore = semaphore; } // End Constructor private class InternalAsyncSemaphoreSlimWrapper : AsyncLock { public InternalAsyncSemaphoreSlimWrapper(System.Threading.SemaphoreSlim semaphore) : base(semaphore) { } // End Constructor } // End Class InternalAsyncSemaphoreSlimWrapper private async System.Threading.Tasks.Task AcquireAsync() { await this.m_semaphore.WaitAsync(); // Asynchronously wait for the semaphore } // End Task AcquireAsync public static async System.Threading.Tasks.Task<AsyncLock> LockAsync(System.Threading.SemaphoreSlim semaphore) { InternalAsyncSemaphoreSlimWrapper wrapper = new InternalAsyncSemaphoreSlimWrapper(semaphore); await wrapper.AcquireAsync(); return wrapper; } // End Function LockAsync async System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync() { this.m_semaphore.Release(); // Release the semaphore when disposed await System.Threading.Tasks.Task.CompletedTask; } // End Task DisposeAsync internal static async System.Threading.Tasks.Task Test() { // private static readonly System.Threading.SemaphoreSlim s_consoleSemaphore = new System.Threading.SemaphoreSlim(1, 1); System.Threading.SemaphoreSlim s_consoleSemaphore = new System.Threading.SemaphoreSlim(1, 1); // because lock BLOCKS the thread when it waits await using (AsyncLock consoleLocker = await AsyncLock.LockAsync(s_consoleSemaphore)) { System.Console.WriteLine("inside the lock !"); } // End Using consoleLocker } // End Task Test } // End Class AsyncLock } // End Namespace This can then be used as follows
private static readonly System.Threading.SemaphoreSlim s_consoleSemaphore = new System.Threading.SemaphoreSlim(1, 1); internal static async System.Threading.Tasks.Task Test() { // no lock possible because lock BLOCKS the thread when it waits // semaphore does not await using (AsyncLock consoleLocker = await AsyncLock.LockAsync(s_consoleSemaphore)) { await System.Console.Out.WriteLineAsync("inside the lock !"); } // End Using consoleLocker } // End Task Test As you can see, new object() from lock is simply replaced with new SemaphoreSlim(1, 1)
The lock-statement for comparison:
private static readonly object s_consoleLock = new object(); internal static void Test() { // no lock possible because lock BLOCKS the thread when it waits // semaphore does not lock(s_consoleLock) { System.Console.WriteLine("inside the lock !"); } // End lock } // End Task Test