await not using current SynchronizationContext

巧了我就是萌 提交于 2019-12-01 06:32:20

There is a common misconception about await, that somehow calling an async-implemented function is treated specially.

However, the await keyword operates on an object, it does not care at all where the awaitable object comes from.

That is, you can always rewrite await Blah(); with var blahTask = Blah(); await blahTask;

So what happens when you rewrite the outer await call that way?

// Synchronization Context leads to main thread;
Task<int> xTask = SomeOtherFunction();
// Synchronization Context has already been set 
// to null by SomeOtherFunction!
int x = await xTask;

And then, there is the other issue: The finally from the inner method is executed in the continuation, meaning that it is executed on the thread pool - so not only you have unset your SynchronizationContext, but your SynchronizationContext will (potentially) be restored at some time in the future, on another thread. However, because I do not really understand the way that the SynchronizationContext is flowed, it is quite possible that the SynchronizationContext is not restored at all, that it is simply set on another thread (remember that SynchronizationContext.Current is thread-local...)

These two issues, combined, would easily explain the randomness that you observe. (That is, you are manipulating quasi-global state from multiple threads...)

The root of the issue is that the await keyword does not allow scheduling of the continuation task.

In general, you simply want to specify "It is not important for the code after the await to be on the same context as the code before await", and in that case, using ConfigureAwait(false) would be appropriate;

async Task SomeOtherFunction() {
    await Blah().ConfigureAwait(false);
}

However, if you absolutely want to specify "I want the code after the await to run on the thread pool" - which is something that should be rare, then you cannot do it with await, but you can do it e.g. with ContinueWith - however, you are going to mix multiple ways of using Task objects, and that can lead to pretty confusing code.

Task SomeOtherFunction() {
    return Blah()
        .ContinueWith(blahTask => WeShouldBeInAThreadPoolThread(),
                      TaskScheduler.Default);
}

I would expect your code to work, but there are a few possible reasons why it's not:

  1. Ensure your SynchronizationContext is current when it executes its continuations.
  2. It's not strictly defined when the SynchronizationContext is captured.
  3. The normal way to run code in a SynchronizationContext is to establish the current one in one method, and then run another (possibly-asynchronous) method that depends on it.
  4. The normal way to avoid the current SynchronizationContext is to append ConfigureAwait(false) to all tasks that are awaited.
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!