Search code examples
c#winformsasync-awaitc#-5.0invokerequired

How do I implement InvokeRequired UI pattern inside an async method?


Let's say I have a Form that tries to deal with a multi-threaded environment; it therefore checks if it's running on the UI thread before any UI modification is done:

partial class SomeForm : Form
{
    public void DoSomethingToUserInterface()
    {
        if (InvokeRequired)
        {
            BeginInvoke(delegate { DoSomethingToUserInterface() });
        }
        else
        {
            … // do the actual work (e.g. manipulate the form or its elements)
        }
    }
}

Now let's say I am performing some lengthy operation inside the part of that method; I'd therefore like to make it asynchronous using async/await.

Given that I should change the method signature to return a Task instead of void (so that exceptions can be caught), how would I implement the part that performs the BeginInvoke? What should it return?

public async Task DoSomethingToUserInterfaceAsync()
{
    if (InvokeRequired)
    {
        // what do I put here?
    }
    {
        … // like before (but can now use `await` expressions)
    }
}

Solution

  • When using async-await with a custom awaiter such as the common TaskAwaiter, the SynchronizationContext is being implicitly captured for you. Later, when the asynchronous method completes, the continuation (any code after the await) is being marshaled back to that same sync context using SynchronizationContext.Post.

    This altogether eliminates the need to use InvokeRequired and other techniques used to mainpulate work on the UI thread. In order to do that, you'll have to trace your method calls all the way to the top level ones and refactor them to use async-await probably.

    But, to address the specific problem as is, what you can do is capture the WinFormSynchronizationContext when your Form initializes:

    partial class SomeForm : Form
    {
        private TaskScheduler _uiTaskScheduler;
        public SomeForm()
        {
            _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
        }
    }
    

    And later use it when you want to await:

    if (InvokeRequired)
    {
        Task uiTask = new Task(() => DoSomethingToUserInterface());
        uiTask.RunSynchronously(_uiTaskScheduler);
    }
    else
    {
        // Do async work
    }