await not using current SynchronizationContext

前端 未结 2 895
隐瞒了意图╮
隐瞒了意图╮ 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: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 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);
    }
    

提交回复
热议问题