Why sync context is not working for await?

2019-05-10 02:30发布

This answer says

by default the await operator will capture the current "context" and use that to resume the async method.

I am trying this code in my console app:

static void Main(string[] args)
{
    Test().Wait();
}

private static async Task Test()
{
    var context = new SynchronizationContext();
    SynchronizationContext.SetSynchronizationContext(context);
    Console.WriteLine("Thread before: " + Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine(await GetResultAsync());
    Console.WriteLine("Thread after: " + Thread.CurrentThread.ManagedThreadId);
}

private static async Task<string> GetResultAsync()
{
    return await Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Thread inside: " + Thread.CurrentThread.ManagedThreadId);
        return "Hello stackoverflow!";
    });
}

... and get this out:

Thread before: 1
Thread inside: 3
Hello stackoverflow!
Thread after: 3

Why? And also how I should set sync context if I want to use the same thread after await?

2条回答
SAY GOODBYE
2楼-- · 2019-05-10 02:48

Why?

new SynchronizationContext() by convention is the same as a null SynchronizationContext. Both "no SyncCtx" and "default SyncCtx" just queue work to the thread pool.

There is not a 1:1 relationship between SynchronizationContext and a specific thread. For example:

  • The WinForms UI SynchronizationContext will queue work to the single UI thread. AFAIK, the WinForms SynchronizationContext is a singleton, so there is a 1:1 mapping in this case.
  • The WPF SynchronizationContext will queue work to its Dispatcher. Last time I checked, WPF will create a new instance for each top-level window, even if they all use the same thread. So there is a N:1 mapping.
  • The thread pool (default/null) SynchronizationContext can queue work to any thread pool thread. If you don't create a default SynchronizationContext instance, there is a 1:N mapping.

And also how I should set sync context if I want to use the same thread after await?

You'll need to use a custom SynchronizationContext. I recommend using my AsyncContext or AsyncContextThread types, since that's not straightforward code to write.

查看更多
看我几分像从前
3楼-- · 2019-05-10 02:49

I am not an expert on this topic, I just have read some tutorials.

Code after await will be run as a task continuation with captured synchronization context. You provided new SynchronizationContext() which uses ThreadPool to execute that code.

Source code: link

Look at Send method in SynchronizationContext class:

public virtual void Post(SendOrPostCallback d, Object state)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
}

So as you see, the continuation will not be run on main thread. It uses ThreadPool.

When GetResultAsync() is done, thread 3 is free to use, and is immediately reused by ThreadPool.QueueUserWorkItem in SynchronizationContext.

So you need to create your own synchronization context for console application.

Didn't read, but maybe this link will help.

查看更多
登录 后发表回答