await not using current SynchronizationContext

前端 未结 2 898
隐瞒了意图╮
隐瞒了意图╮ 2021-01-14 02:52

I\'m getting confusing behavior when using a different SynchronizationContext inside an async function than outside.

Most of my program\'s code uses a custom Synchro

相关标签:
2条回答
  • 2021-01-14 03:13

    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.
    0 讨论(0)
  • 2021-01-14 03:33

    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);
    }
    
    0 讨论(0)
提交回复
热议问题