using System.Collections.Generic;
using System.Threading.Tasks;
public static int CurrentThreadId => Thread.CurrentThread.ManagedThreadId;
AsyncLock asyncLock = new AsyncLock();
public static async Task Main(string []args){
await new Program().MainAsync();
public async Task MainAsync(){
await using(await asyncLock.LockAsync()){
Console.WriteLine($"1. {nameof(MainAsync)} starting on thread {CurrentThreadId}");
var childTask = ChildAsync(delay: TimeSpan.FromMilliseconds(100));
await Task.Delay(TimeSpan.FromMilliseconds(101)).ConfigureAwait(false);
Console.WriteLine($"4. {nameof(MainAsync)} continuing on thread {CurrentThreadId}");
await using(await asyncLock.LockAsync()){
Console.WriteLine($"In Critical section on main thread {CurrentThreadId}");
Console.WriteLine($"6. {nameof(MainAsync)} finished on main thread");
async Task ChildAsync(TimeSpan delay)
Console.WriteLine($"2. {nameof(ChildAsync)} starting on thread {CurrentThreadId}");
await Task.Delay(delay).ConfigureAwait(false);
await using (await asyncLock.LockAsync())
Console.WriteLine($"3. {nameof(ChildAsync)} continuing on thread {CurrentThreadId}");
Console.WriteLine($"In Critical section on child thread {CurrentThreadId}");
Console.WriteLine($"5. {nameof(ChildAsync)} finished on child thread");
class AsyncLock : IAsyncDisposable {
private object locker = new object();
private string path = "";
private AsyncLocal<string> contextPath;
private Random random = new Random();
private List<TaskCompletionSource> waiters = new List<TaskCompletionSource>();
contextPath = new AsyncLocal<string>();
public Task<IAsyncDisposable> LockAsync(){
TaskCompletionSource waiter = null;
char nextCharacter = (char)((int)'a' + random.Next('z' - 'a'));
if(contextPath.Value == path){
path = path + nextCharacter;
contextPath.Value = path;
waiter = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
if(!taken && waiter != null){
return waiter.Task.ContinueWith(t => LockAsync()).Result;
return Task.FromResult((IAsyncDisposable)this);
public ValueTask DisposeAsync (){
path = path.Substring(0, path.Length - 1);
contextPath.Value = path;
foreach(var waiter in waiters){
Monitor.PulseAll(locker);
return ValueTask.CompletedTask;