using System.Threading.Tasks;
public static void Main()
var asyncLock = new StephenCleary.RecursiveAsyncLock();
for (var i = 0; i < 100000; ++i)
using (await asyncLock.LockAsync(CancellationToken.None))
using (await asyncLock.LockAsync(CancellationToken.None))
using (await asyncLock.LockAsync(CancellationToken.None))
Console.WriteLine("Not okay");
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Runtime.Remoting.Messaging;
using System.Threading.Tasks;
static class AsyncLockTracker
private static readonly string slotName = Guid.NewGuid().ToString("N");
private static IImmutableDictionary<RecursiveAsyncLock, Tuple<int, Task<IDisposable>>> OwnedLocks
var ret = CallContext.LogicalGetData(slotName) as ImmutableDictionary<RecursiveAsyncLock, Tuple<int, Task<IDisposable>>>;
return ImmutableDictionary.Create<RecursiveAsyncLock, Tuple<int, Task<IDisposable>>>();
CallContext.LogicalSetData(slotName, value);
public static bool Contains(RecursiveAsyncLock mutex)
return OwnedLocks.ContainsKey(mutex);
public static Task<IDisposable> Lookup(RecursiveAsyncLock mutex)
Tuple<int, Task<IDisposable>> value;
if (OwnedLocks.TryGetValue(mutex, out value))
public static void Add(RecursiveAsyncLock mutex, Task<IDisposable> key)
Tuple<int, Task<IDisposable>> value;
if (!OwnedLocks.TryGetValue(mutex, out value))
value = Tuple.Create(0, key);
OwnedLocks = OwnedLocks.SetItem(mutex, Tuple.Create(value.Item1 + 1, value.Item2));
public static void Remove(RecursiveAsyncLock mutex)
var value = OwnedLocks[mutex];
OwnedLocks = OwnedLocks.Remove(mutex);
value.Item2.Result.Dispose();
OwnedLocks = OwnedLocks.SetItem(mutex, Tuple.Create(value.Item1 - 1, value.Item2));
public sealed class RecursiveAsyncLock
private readonly AsyncLock mutex;
public RecursiveAsyncLock()
public RecursiveAsyncLock(IAsyncWaitQueue<IDisposable> queue)
mutex = new AsyncLock(queue);
public int Id { get { return mutex.Id; } }
public RecursiveLockAwaitable LockAsync(CancellationToken token)
var key = AsyncLockTracker.Lookup(this);
key = mutex.LockAsync(token).AsTask();
return new RecursiveLockAwaitable(key, this);
public RecursiveLockAwaitable LockAsync()
return LockAsync(CancellationToken.None);
public sealed class RecursiveLockAwaitable : INotifyCompletion
private readonly Task<IDisposable> _key;
private readonly TaskAwaiter<IDisposable> _awaiter;
private readonly RecursiveAsyncLock _mutex;
public RecursiveLockAwaitable(Task<IDisposable> key, RecursiveAsyncLock mutex)
_awaiter = key.GetAwaiter();
public RecursiveLockAwaitable GetAwaiter()
get { return _awaiter.IsCompleted; }
public IDisposable GetResult()
var ret = _awaiter.GetResult();
return new KeyDisposable(_key, _mutex);
public void OnCompleted(Action continuation)
_awaiter.OnCompleted(continuation);
private sealed class KeyDisposable : IDisposable
private RecursiveAsyncLock _mutex;
public KeyDisposable(Task<IDisposable> keyTask, RecursiveAsyncLock mutex)
AsyncLockTracker.Add(mutex, keyTask);
AsyncLockTracker.Remove(_mutex);