CSharp · Console
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
class Program
{
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");
await childTask;
}
}
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>();
public AsyncLock(){
contextPath = new AsyncLocal<string>();
contextPath.Value = "";
}
public Task<IAsyncDisposable> LockAsync(){
bool taken = false;
TaskCompletionSource waiter = null;
while(!taken){
lock(locker){
char nextCharacter = (char)((int)'a' + random.Next('z' - 'a'));
if(contextPath.Value == path){
path = path + nextCharacter;
contextPath.Value = path;
taken = true;
}else{
waiter = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
waiters.Add(waiter);
}
}
if(!taken && waiter != null){
return waiter.Task.ContinueWith(t => LockAsync()).Result;
}
}
return Task.FromResult((IAsyncDisposable)this);
}
public ValueTask DisposeAsync (){
lock(locker){
if(path.Length == 1){
path = "";
}else{
path = path.Substring(0, path.Length - 1);
}
contextPath.Value = path;
foreach(var waiter in waiters){
waiter.SetResult();
}
waiters.Clear();
Monitor.PulseAll(locker);
}
return ValueTask.CompletedTask;
}
}