Created
March 24, 2017 13:13
-
-
Save StephenCleary/7dd1c0fc2a6594ba0ed7fb7ad6b590d6 to your computer and use it in GitHub Desktop.
LockAsync abstraction around SemaphoreSlim
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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