Skip to content

Instantly share code, notes, and snippets.

@StephenCleary
Created March 24, 2017 13:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save StephenCleary/7dd1c0fc2a6594ba0ed7fb7ad6b590d6 to your computer and use it in GitHub Desktop.
Save StephenCleary/7dd1c0fc2a6594ba0ed7fb7ad6b590d6 to your computer and use it in GitHub Desktop.
LockAsync abstraction around SemaphoreSlim
using System;
using System.Threading;
/// <summary>
/// A base class for disposables that need exactly-once semantics in a threadsafe way. All disposals of this instance block until the disposal is complete.
/// </summary>
/// <remarks>
/// <para>If <see cref="Dispose()"/> is called multiple times, only the first call will execute the disposal code. Other calls to <see cref="Dispose()"/> will wait for the disposal to complete.</para>
/// </remarks>
public sealed class AnonymousDisposable : IDisposable
{
/// <summary>
/// The action. If this is <c>null</c>, then this instance is disposed (or disposing).
/// </summary>
private Action _action;
/// <summary>
/// The signal set when the disposal is complete.
/// </summary>
private readonly ManualResetEventSlim _mre = new ManualResetEventSlim();
/// <summary>
/// Creates a disposable for the specified action.
/// </summary>
/// <param name="action">The action executed on <see cref="Dispose()"/>.</param>
public AnonymousDisposable(Action action)
{
_action = action;
}
/// <summary>
/// Disposes this instance.
/// </summary>
/// <remarks>
/// <para>If <see cref="Dispose()"/> is called multiple times, only the first call will execute the disposal code. Other calls to <see cref="Dispose()"/> will wait for the disposal to complete.</para>
/// </remarks>
public void Dispose()
{
var action = Interlocked.Exchange(ref _action, null);
if (action == null)
{
_mre.Wait();
return;
}
try
{
action();
}
finally
{
_mre.Set();
}
}
}
using System;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
/// <summary>
/// An awaitable wrapper around a task whose result is disposable. The wrapper is not disposable, so this prevents usage errors like "using (MyAsync())" when the appropriate usage should be "using (await MyAsync())".
/// </summary>
/// <typeparam name="T">The type of the result of the underlying task.</typeparam>
public struct AwaitableDisposable<T> where T : IDisposable
{
/// <summary>
/// The underlying task.
/// </summary>
private readonly Task<T> _task;
/// <summary>
/// Initializes a new awaitable wrapper around the specified task.
/// </summary>
/// <param name="task">The underlying task to wrap. This may not be <c>null</c>.</param>
public AwaitableDisposable(Task<T> task)
{
if (task == null)
throw new ArgumentNullException(nameof(task));
_task = task;
}
/// <summary>
/// Returns the underlying task.
/// </summary>
public Task<T> AsTask()
{
return _task;
}
/// <summary>
/// Implicit conversion to the underlying task.
/// </summary>
/// <param name="source">The awaitable wrapper.</param>
public static implicit operator Task<T>(AwaitableDisposable<T> source)
{
return source.AsTask();
}
/// <summary>
/// Infrastructure. Returns the task awaiter for the underlying task.
/// </summary>
public TaskAwaiter<T> GetAwaiter()
{
return _task.GetAwaiter();
}
/// <summary>
/// Infrastructure. Returns a configured task awaiter for the underlying task.
/// </summary>
/// <param name="continueOnCapturedContext">Whether to attempt to marshal the continuation back to the captured context.</param>
public ConfiguredTaskAwaitable<T> ConfigureAwait(bool continueOnCapturedContext)
{
return _task.ConfigureAwait(continueOnCapturedContext);
}
}
using System;
using System.Threading;
using System.Threading.Tasks;
public static class SemaphoreSlimExtensions
{
public static AwaitableDisposable<IDisposable> LockAsync(this SemaphoreSlim semaphore)
{
return new AwaitableDisposable<IDisposable>(DoLockAsync(semaphore));
}
private static async Task<IDisposable> DoLockAsync(SemaphoreSlim semaphore)
{
await semaphore.WaitAsync().ConfigureAwait(false);
return new AnonymousDisposable(() => semaphore.Release());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment