Ожидание завершения async void возможно, но легально ли это?
Дисклеймер: велосипед изобрел не я, а разработчики ASP.NET
Не так давно наткнулся на статью от S. Cleary про варианты решения проблем при инвоке событий, в которых используются async void обработчики, и от них же требуется обратная связь через аргументы события.
И меня до жути заинтересовала часть про контекст синхронизации, и я решил проверить, как это работает. Создал вот такой контекст синхронизации.
public class MyContext : SynchronizationContext, IAsyncDisposable
{
private SynchronizationContext _context;
private TaskCompletionSource _src;
private int count;
private Task _task;
public MyContext(SynchronizationContext context)
{
_context = context;
_task = Task.CompletedTask;
SetSynchronizationContext(this);
}
public override void Post(SendOrPostCallback d, object state)
{
if (_context != null)
_context.Post(d, state);
else
Task.Run(() =>
{
SetSynchronizationContext(this);
d(state);
SetSynchronizationContext(null);
});
}
public override void Send(SendOrPostCallback d, object state)
{
if (_context != null)
_context.Send(d, state);
else
d(state);
}
public override void OperationStarted()
{
lock (this)
{
if (count++ == 0)
{
_src = new TaskCompletionSource();
_task = _src.Task;
}
}
}
public override void OperationCompleted()
{
lock (this)
{
if (--count == 0)
_src.SetResult();
}
}
public ValueTask DisposeAsync()
{
SetSynchronizationContext(_context);
return new ValueTask(_task);
}
}
Далее вот так протестировал
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Start");
await RunAsyncVoid(() => Method());
Console.WriteLine("Done");
Console.ReadKey();
}
private static async Task RunAsyncVoid(Action method)
{
await using var context = new MyContext(SynchronizationContext.Current);
method();
}
static async void Method()
{
Console.WriteLine("Enter method 1");
Method2();
await Task.Delay(1000);
Method3();
Console.WriteLine("Finish method 1");
}
static async void Method2()
{
Console.WriteLine("Enter method 2");
await Task.Delay(1000);
Console.WriteLine("Finish method 2");
}
static async void Method3()
{
Console.WriteLine("Enter method 3");
await Task.Delay(1000);
Console.WriteLine("Finish method 3");
}
}
И получил вот такой вывод в консоль.
Start
Enter method 1
Enter method 2
Finish method 2
Enter method 3
Finish method 1
Finish method 3
Done
Про плохую практику использования async void и все подводные камни я в курсе. Исключения перехватить оттуда невозможно - это факт. Но это всего-лишь тестовый пример, а речь на самом деле про обработчики событий и вызов события, среди которых могут быть и синхронные и асинхронные, вперемешку.
Вопрос академический: легально ли вот так подождать завершения async void метода, и какие грабли могут встретиться на пути? Просто я уже мысленно вижу как доработаю свой RelayCommand для WPF, но что-то боязно.