Task's continuation (built by async/await) is

2019-07-31 11:22发布

问题:

Assume I have a simple C# Console Application:

class Program
{
    static async void func()
    {
        Thread.CurrentThread.Name = "main";
        await Task.Run(() =>
        {
            Thread.CurrentThread.Name = "child";
            Thread.Sleep(5000);
        });
        Console.WriteLine("continuation is running on {0} thread", Thread.CurrentThread.Name);
    }

    static void Main(string[] args)
    {
        func();
        Thread.Sleep(10000);
    }
}

When 5000 ms pass, we see the "continuation is running on child thread" message. When another 5000 ms pass, main thread finishes its work and application is closed. It looks rather logical: asynchronous task and its continuation are running on the same child thread.

But assume now I have a simple WPF application:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    async private void mainWnd_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        Thread.CurrentThread.Name = "main";
        await Task.Run(() =>
        {
            Thread.CurrentThread.Name = "child";
            Thread.Sleep(5000);
        });
        this.Title = string.Format("continuation is running on {0} thread", Thread.CurrentThread.Name);
    }

    private void mainWnd_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
        Thread.Sleep(10000);
    }
}

Now when we press left mouse button and 5000 ms pass, we see "continuation is running on main thread" title. Moreover, if we press left button and then right button, application first will run mainWnd_MouseLeftButtonDown handler, then mainWnd_MouseRightButtonDown handler (on main thread), main thread will sleep for 10000 ms, and then continuation of asynchronous task from mainWnd_MouseLeftButtonDown will be still performed on main thread.

Why does async-await mechanism differ for these two situations?

I know that in WPF method can be explicitly run on UI thread through Dispatcher.Invoke, but async-await mechanism isn't WPF-specific, so its behavior should be equal in any kind of application, shouldn't it?

Any help will be appreciated.

回答1:

async-await respects the current scope's SynchronizationContext. That means that the context (if it exists) is captured when the asynchronous operation starts and when it ends the continuation is scheduled on the captured context.

UI applications (WPF/Winforms) use a SynchronizationContext that allows only for the main (UI) thread to interact with the UI elements, so it seamlessly works with async-await.

ASP.Net also has it's own SynchronizationContext called AspNetSynchronizationContext (surprisingly). So it isn't necessarily about UI or Single Thread Apartments.


If you want to disable that useful SynchronizationContext capturing you just need to use ConfigureAwait:

await Task.Run(() =>
{
    Thread.CurrentThread.Name = "child";
    Thread.Sleep(5000);
}).ConfigureAwait(false);

More on SynchronizationContexts: It's All About the SynchronizationContext