Skip to main content
added 96 characters in body
Source Link
Jez
  • 30.5k
  • 37
  • 155
  • 261

I created a MutexAsyncable class, inspired by Stephen Toub's AsyncLock implementation (discussion at this blog post), which can be used as a drop-in replacement for a lock statement in either sync or async code:

I created a MutexAsyncable class, inspired by Stephen Toub's AsyncLock implementation (discussion at this blog post):

I created a MutexAsyncable class, inspired by Stephen Toub's AsyncLock implementation (discussion at this blog post), which can be used as a drop-in replacement for a lock statement in either sync or async code:

deleted 47 characters in body
Source Link
Jez
  • 30.5k
  • 37
  • 155
  • 261
using System; using System.Threading; using System.Threading.Tasks; namespace UtilsCommon.Lib; /// <summary> /// Class that provides (optionally async-safe) locking using an internal semaphore. /// Use this in place of a lock() {...} construction. /// Bear in mind that all code executed inside the worker must finish before the next /// thread is able to start executing it, so long-running code should be avoided inside /// the worker if at all possible. /// /// Example usage for sync: /// using (mutex.LockSync()) { /// // ... code here which is synchronous and handles a shared resource ... /// return[ result]; /// } /// /// ... or for async: /// using (await mutex.LockAsync()) { /// // ... code here which can use await calls and handle a shared resource ... /// return[ result]; /// } /// </summary> public sealed class MutexAsyncable { // Based on Stephen Toub's AsyncLock implementation #region ClassesInternal classes private sealed class Releaser : IDisposable { private readonly MutexAsyncable _toRelease; internal Releaser(MutexAsyncable toRelease) { _toRelease = toRelease; } public void Dispose() { _toRelease._semaphore.Release(); } } #endregion private readonly SemaphoreSlim _semaphore = new(1, 1); private readonly Task<IDisposable> _releaser; public MutexAsyncable() { _releaser = Task.FromResult((IDisposable)new Releaser(this)); } public IDisposable LockSync() { _semaphore.Wait(); return _releaser.Result; } public Task<IDisposable> LockAsync() { var wait = _semaphore.WaitAsync(); if (wait.IsCompleted) { return _releaser; } else { // Return Task<IDisposable> which completes once WaitAsync does return wait.ContinueWith( (_, state) => (IDisposable)state!, _releaser.Result, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default ); } } } 
using System; using System.Threading; using System.Threading.Tasks; namespace UtilsCommon.Lib; /// <summary> /// Class that provides (optionally async-safe) locking using an internal semaphore. /// Use this in place of a lock() {...} construction. /// Bear in mind that all code executed inside the worker must finish before the next /// thread is able to start executing it, so long-running code should be avoided inside /// the worker if at all possible. /// /// Example usage for sync: /// using (mutex.LockSync()) { /// // ... code here which is synchronous and handles a shared resource ... /// return[ result]; /// } /// /// ... or for async: /// using (await mutex.LockAsync()) { /// // ... code here which can use await calls and handle a shared resource ... /// return[ result]; /// } /// </summary> public sealed class MutexAsyncable { // Based on Stephen Toub's AsyncLock implementation #region Classes private sealed class Releaser : IDisposable { private readonly MutexAsyncable _toRelease; internal Releaser(MutexAsyncable toRelease) { _toRelease = toRelease; } public void Dispose() { _toRelease._semaphore.Release(); } } #endregion private readonly SemaphoreSlim _semaphore = new(1, 1); private readonly Task<IDisposable> _releaser; public MutexAsyncable() { _releaser = Task.FromResult((IDisposable)new Releaser(this)); } public IDisposable LockSync() { _semaphore.Wait(); return _releaser.Result; } public Task<IDisposable> LockAsync() { var wait = _semaphore.WaitAsync(); if (wait.IsCompleted) { return _releaser; } else { // Return Task<IDisposable> which completes once WaitAsync does return wait.ContinueWith( (_, state) => (IDisposable)state!, _releaser.Result, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default ); } } } 
using System; using System.Threading; using System.Threading.Tasks; namespace UtilsCommon.Lib; /// <summary> /// Class that provides (optionally async-safe) locking using an internal semaphore. /// Use this in place of a lock() {...} construction. /// Bear in mind that all code executed inside the worker must finish before the next /// thread is able to start executing it, so long-running code should be avoided inside /// the worker if at all possible. /// /// Example usage for sync: /// using (mutex.LockSync()) { /// // ... code here which is synchronous and handles a shared resource ... /// return[ result]; /// } /// /// ... or for async: /// using (await mutex.LockAsync()) { /// // ... code here which can use await calls and handle a shared resource ... /// return[ result]; /// } /// </summary> public sealed class MutexAsyncable { #region Internal classes private sealed class Releaser : IDisposable { private readonly MutexAsyncable _toRelease; internal Releaser(MutexAsyncable toRelease) { _toRelease = toRelease; } public void Dispose() { _toRelease._semaphore.Release(); } } #endregion private readonly SemaphoreSlim _semaphore = new(1, 1); private readonly Task<IDisposable> _releaser; public MutexAsyncable() { _releaser = Task.FromResult((IDisposable)new Releaser(this)); } public IDisposable LockSync() { _semaphore.Wait(); return _releaser.Result; } public Task<IDisposable> LockAsync() { var wait = _semaphore.WaitAsync(); if (wait.IsCompleted) { return _releaser; } else { // Return Task<IDisposable> which completes once WaitAsync does return wait.ContinueWith( (_, state) => (IDisposable)state!, _releaser.Result, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default ); } } } 
Added new MutexAsyncable
Source Link
Jez
  • 30.5k
  • 37
  • 155
  • 261

Inspired byI created a this answerMutexAsyncable class, inspired by Stephen Toub's AsyncLock implementation (discussion at this blog post):

using System; using System.Threading; using System.Threading.Tasks; namespace UtilsCommon.Lib; /// <summary> /// Class that provides (optionally async-safe) locking using an internal semaphore. /// Use this in place of a lock() {...} construction. /// Bear in mind that all code executed inside the worker must finish before the next /// thread is able to start executing it, so long-running code should be avoided inside /// the worker if at all possible. /// /// Example usage for sync: /// using (mutex.LockSync()) { /// // ... code here which is synchronous and handles a shared resource ... /// return[ result]; /// } /// /// ... or for async: /// using (await mutex.LockAsync()) { /// // ... code here which can use await calls and handle a shared resource ... /// return[ result]; /// } /// </summary> public sealed class MutexAsyncable { // Based on Stephen Toub's AsyncLock implementation #region Classes private sealed class Releaser : IDisposable { private readonly MutexAsyncable _toRelease; internal Releaser(MutexAsyncable toRelease) { _toRelease = toRelease; } public void Dispose() { _toRelease._semaphore.Release(); } } #endregion private readonly SemaphoreSlim _semaphore = new(1, 1); private readonly Task<IDisposable> _releaser; public MutexAsyncable() { _releaser = Task.FromResult((IDisposable)new Releaser(this)); } public IDisposable LockSync() { _semaphore.Wait(); return _releaser.Result; } public Task<IDisposable> LockAsync() { var wait = _semaphore.WaitAsync(); if (wait.IsCompleted) { return _releaser; } else { // Return Task<IDisposable> which completes once WaitAsync does return wait.ContinueWith( (_, state) => (IDisposable)state!, _releaser.Result, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default ); } } } 

It's safe to use the above if you're using .NET 5+ because that won't ever throw ThreadAbortException.

I also created an extended SemaphoreLocker toclass, inspired by this answer, which can be a general-purpose replacement for lock, which can be usedusable either synchronously or asynchronously. It is less efficient than the above MutexAsyncable and allocates more resources, although it has the benefit of forcing the worker code to release the lock once it's finished (technically, the IDisposable returned by the MutexAsyncable could not get disposed by calling code and cause deadlock). It also has extra try/finally code to deal with the possibility of ThreadAbortException, so should be usable in earlier .NET versions:

using System; using System.Threading; using System.Threading.Tasks; namespace Mystikaze.CommonUtilsCommon.Lib; /// <summary> /// Class that provides (optionally async-safe) locking using an internal semaphore. /// Use this in place of a lock() {...} construction. /// Bear in mind that all code executed inside the worker must finish before the next thread is able to /// start executing it, so long-running code should be avoided inside the worker if at all possible. /// /// Example usage: /// [var result = ]await _locker.LockAsync(async () => { /// // ... code here which can use await calls and handle a shared resource one-thread-at-a-time ... /// return[ result]; /// }); /// /// ... or for sync: /// [var result = ]_locker.LockSync(() => { /// // ... code here which is synchronous and handles a shared resource one-thread-at-a-time ... /// return[ result]; /// }); /// </summary> public sealed class SemaphoreLocker : IDisposable { private readonly SemaphoreSlim _semaphore = new(1, 1); /// <summary> /// Runs the worker lambda in a locked context. /// </summary> /// <typeparam name="T">The type of the worker lambda's return value.</typeparam> /// <param name="worker">The worker lambda to be executed.</param> public T LockSync<T>(Func<T> worker) { var isTaken = false; try { do { try { } finally { isTaken = _semaphore.Wait(TimeSpan.FromSeconds(1)); } } while (!isTaken); return worker(); } finally { if (isTaken) { _semaphore.Release(); } } } /// <inheritdoc cref="LockSync{T}(Func{T})" /> public void LockSync(Action worker) { var isTaken = false; try { do { try { } finally { isTaken = _semaphore.Wait(TimeSpan.FromSeconds(1)); } } while (!isTaken); worker(); } finally { if (isTaken) { _semaphore.Release(); } } } /// <summary> /// Runs the worker lambda in an async-safe locked context. /// </summary> /// <typeparam name="T">The type of the worker lambda's return value.</typeparam> /// <param name="worker">The worker lambda to be executed.</param> public async Task<T> LockAsync<T>(Func<Task<T>> worker) { var isTaken = false; try { do { try { } finally { isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1)); } } while (!isTaken); return await worker(); } finally { if (isTaken) { _semaphore.Release(); } } } /// <inheritdoc cref="LockAsync{T}(Func{Task{T}})" /> public async Task LockAsync(Func<Task> worker) { var isTaken = false; try { do { try { } finally { isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1)); } } while (!isTaken); await worker(); } finally { if (isTaken) { _semaphore.Release(); } } } /// <summary> /// Releases all resources used by the current instance of the SemaphoreLocker class. /// </summary> public void Dispose() { _semaphore.Dispose(); } } 

Inspired by this answer, I extended SemaphoreLocker to be a general-purpose replacement for lock, which can be used either synchronously or asynchronously:

using System; using System.Threading; using System.Threading.Tasks; namespace Mystikaze.Common.Lib; /// <summary> /// Class that provides (optionally async-safe) locking using an internal semaphore. /// Use this in place of a lock() {...} construction. /// Bear in mind that all code executed inside the worker must finish before the next thread is able to /// start executing it, so long-running code should be avoided inside the worker if at all possible. /// /// Example usage: /// [var result = ]await _locker.LockAsync(async () => { /// // ... code here which can use await calls and handle a shared resource one-thread-at-a-time ... /// return[ result]; /// }); /// /// ... or for sync: /// [var result = ]_locker.LockSync(() => { /// // ... code here which is synchronous and handles a shared resource one-thread-at-a-time ... /// return[ result]; /// }); /// </summary> public sealed class SemaphoreLocker : IDisposable { private readonly SemaphoreSlim _semaphore = new(1, 1); /// <summary> /// Runs the worker lambda in a locked context. /// </summary> /// <typeparam name="T">The type of the worker lambda's return value.</typeparam> /// <param name="worker">The worker lambda to be executed.</param> public T LockSync<T>(Func<T> worker) { var isTaken = false; try { do { try { } finally { isTaken = _semaphore.Wait(TimeSpan.FromSeconds(1)); } } while (!isTaken); return worker(); } finally { if (isTaken) { _semaphore.Release(); } } } /// <inheritdoc cref="LockSync{T}(Func{T})" /> public void LockSync(Action worker) { var isTaken = false; try { do { try { } finally { isTaken = _semaphore.Wait(TimeSpan.FromSeconds(1)); } } while (!isTaken); worker(); } finally { if (isTaken) { _semaphore.Release(); } } } /// <summary> /// Runs the worker lambda in an async-safe locked context. /// </summary> /// <typeparam name="T">The type of the worker lambda's return value.</typeparam> /// <param name="worker">The worker lambda to be executed.</param> public async Task<T> LockAsync<T>(Func<Task<T>> worker) { var isTaken = false; try { do { try { } finally { isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1)); } } while (!isTaken); return await worker(); } finally { if (isTaken) { _semaphore.Release(); } } } /// <inheritdoc cref="LockAsync{T}(Func{Task{T}})" /> public async Task LockAsync(Func<Task> worker) { var isTaken = false; try { do { try { } finally { isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1)); } } while (!isTaken); await worker(); } finally { if (isTaken) { _semaphore.Release(); } } } /// <summary> /// Releases all resources used by the current instance of the SemaphoreLocker class. /// </summary> public void Dispose() { _semaphore.Dispose(); } } 

I created a MutexAsyncable class, inspired by Stephen Toub's AsyncLock implementation (discussion at this blog post):

using System; using System.Threading; using System.Threading.Tasks; namespace UtilsCommon.Lib; /// <summary> /// Class that provides (optionally async-safe) locking using an internal semaphore. /// Use this in place of a lock() {...} construction. /// Bear in mind that all code executed inside the worker must finish before the next /// thread is able to start executing it, so long-running code should be avoided inside /// the worker if at all possible. /// /// Example usage for sync: /// using (mutex.LockSync()) { /// // ... code here which is synchronous and handles a shared resource ... /// return[ result]; /// } /// /// ... or for async: /// using (await mutex.LockAsync()) { /// // ... code here which can use await calls and handle a shared resource ... /// return[ result]; /// } /// </summary> public sealed class MutexAsyncable { // Based on Stephen Toub's AsyncLock implementation #region Classes private sealed class Releaser : IDisposable { private readonly MutexAsyncable _toRelease; internal Releaser(MutexAsyncable toRelease) { _toRelease = toRelease; } public void Dispose() { _toRelease._semaphore.Release(); } } #endregion private readonly SemaphoreSlim _semaphore = new(1, 1); private readonly Task<IDisposable> _releaser; public MutexAsyncable() { _releaser = Task.FromResult((IDisposable)new Releaser(this)); } public IDisposable LockSync() { _semaphore.Wait(); return _releaser.Result; } public Task<IDisposable> LockAsync() { var wait = _semaphore.WaitAsync(); if (wait.IsCompleted) { return _releaser; } else { // Return Task<IDisposable> which completes once WaitAsync does return wait.ContinueWith( (_, state) => (IDisposable)state!, _releaser.Result, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default ); } } } 

It's safe to use the above if you're using .NET 5+ because that won't ever throw ThreadAbortException.

I also created an extended SemaphoreLocker class, inspired by this answer, which can be a general-purpose replacement for lock, usable either synchronously or asynchronously. It is less efficient than the above MutexAsyncable and allocates more resources, although it has the benefit of forcing the worker code to release the lock once it's finished (technically, the IDisposable returned by the MutexAsyncable could not get disposed by calling code and cause deadlock). It also has extra try/finally code to deal with the possibility of ThreadAbortException, so should be usable in earlier .NET versions:

using System; using System.Threading; using System.Threading.Tasks; namespace UtilsCommon.Lib; /// <summary> /// Class that provides (optionally async-safe) locking using an internal semaphore. /// Use this in place of a lock() {...} construction. /// Bear in mind that all code executed inside the worker must finish before the next thread is able to /// start executing it, so long-running code should be avoided inside the worker if at all possible. /// /// Example usage: /// [var result = ]await _locker.LockAsync(async () => { /// // ... code here which can use await calls and handle a shared resource one-thread-at-a-time ... /// return[ result]; /// }); /// /// ... or for sync: /// [var result = ]_locker.LockSync(() => { /// // ... code here which is synchronous and handles a shared resource one-thread-at-a-time ... /// return[ result]; /// }); /// </summary> public sealed class SemaphoreLocker : IDisposable { private readonly SemaphoreSlim _semaphore = new(1, 1); /// <summary> /// Runs the worker lambda in a locked context. /// </summary> /// <typeparam name="T">The type of the worker lambda's return value.</typeparam> /// <param name="worker">The worker lambda to be executed.</param> public T LockSync<T>(Func<T> worker) { var isTaken = false; try { do { try { } finally { isTaken = _semaphore.Wait(TimeSpan.FromSeconds(1)); } } while (!isTaken); return worker(); } finally { if (isTaken) { _semaphore.Release(); } } } /// <inheritdoc cref="LockSync{T}(Func{T})" /> public void LockSync(Action worker) { var isTaken = false; try { do { try { } finally { isTaken = _semaphore.Wait(TimeSpan.FromSeconds(1)); } } while (!isTaken); worker(); } finally { if (isTaken) { _semaphore.Release(); } } } /// <summary> /// Runs the worker lambda in an async-safe locked context. /// </summary> /// <typeparam name="T">The type of the worker lambda's return value.</typeparam> /// <param name="worker">The worker lambda to be executed.</param> public async Task<T> LockAsync<T>(Func<Task<T>> worker) { var isTaken = false; try { do { try { } finally { isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1)); } } while (!isTaken); return await worker(); } finally { if (isTaken) { _semaphore.Release(); } } } /// <inheritdoc cref="LockAsync{T}(Func{Task{T}})" /> public async Task LockAsync(Func<Task> worker) { var isTaken = false; try { do { try { } finally { isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1)); } } while (!isTaken); await worker(); } finally { if (isTaken) { _semaphore.Release(); } } } /// <summary> /// Releases all resources used by the current instance of the SemaphoreLocker class. /// </summary> public void Dispose() { _semaphore.Dispose(); } } 
added 199 characters in body
Source Link
Jez
  • 30.5k
  • 37
  • 155
  • 261
Loading
Source Link
Jez
  • 30.5k
  • 37
  • 155
  • 261
Loading