3

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?

Community
  • 1
  • 1
hcp
  • 3,268
  • 6
  • 26
  • 41
  • They use the term context here explicitly because not all contexts are bound to a single thread. In a console application, there are no built-in reasons to favour one thread ahead of others (unlike UI frameworks with a UI thread). – Damien_The_Unbeliever May 19 '17 at 11:24

2 Answers2

5

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    Actually, in WPF `SynchronizationContext.Current` is never the same after `await`, where the task completes asynchronously. That's because `Dispatcher` executes every callback on a unique instance of `DispatcherSynchronizationContext`. Kind of in controversy with `continueOnCapturedContext` argument of `ConfigureAwait`, IMO. – noseratio May 19 '17 at 13:08
  • Weird! That behavior has changed since a few years ago, then. – Stephen Cleary May 19 '17 at 14:36
1

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.

apocalypse
  • 5,764
  • 9
  • 47
  • 95