How to implement the OnCompleted method of a custom awaiter correctly?

后端 未结 3 1097
灰色年华
灰色年华 2021-02-10 11:19

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:



        
3条回答
  •  青春惊慌失措
    2021-02-10 11:40

    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.

提交回复
热议问题