I have a custom awaitable type and the problem is that the continuation resumes on a different thread, which causes problems in UIs such as WinForms/WPF/MVC/etc:
This proves that the continuation runs on the captured context:
public class MyAwaitable
{
private volatile bool finished;
public bool IsFinished => finished;
public MyAwaitable(bool finished) => this.finished = finished;
public void Finish() => finished = true;
public MyAwaiter GetAwaiter() => new MyAwaiter(this);
}
public class MyAwaiter : INotifyCompletion
{
private readonly MyAwaitable awaitable;
private readonly SynchronizationContext capturedContext = SynchronizationContext.Current;
public MyAwaiter(MyAwaitable awaitable) => this.awaitable = awaitable;
public bool IsCompleted => awaitable.IsFinished;
public int GetResult()
{
SpinWait.SpinUntil(() => awaitable.IsFinished);
return new Random().Next();
}
public void OnCompleted(Action continuation)
{
if (capturedContext != null) capturedContext.Post(state => continuation(), null);
else continuation();
}
}
public class MySynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
Console.WriteLine("Posted to synchronization context");
d(state);
}
}
class Program
{
static async Task Main()
{
SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext());
var awaitable = new MyAwaitable(false);
var timer = new Timer(_ => awaitable.Finish(), null, 100, -1);
var result = await awaitable;
Console.WriteLine(result);
}
}
Output:
Posted to synchronization context
124762545
But you are not posting the continuation to the synchronization context.
You're posting scheduling the execution of the continuation on another thread.
The scheduling runs on the synchronization context but continuation itself doesn't. Thus your problems.
You can read this to understand how it works.