等待不使用当前的SynchronizationContext

时间:2014-10-07 02:06:33

标签: c# .net synchronizationcontext

在异步函数中使用不同的SynchronizationContext而不是外部时,我会遇到令人困惑的行为。

我的大多数程序代码都使用自定义的SynchronizationContext,它只是将SendOrPostCallbacks排队并在主线程中的特定已知点调用它们。我在开始的时候设置了这个自定义的SynchronizationContext,当我只使用这个时,一切正常。

我遇到的问题是我有一些函数,我希望它们等待继续在线程池中运行。

void BeginningOfTime() {
    // MyCustomContext queues each endOrPostCallback and runs them all at a known point in the main thread.
    SynchronizationContext.SetSynchronizationContext( new MyCustomContext() ); 


    // ... later on in the code, wait on something, and it should continue inside 
    // the main thread where MyCustomContext runs everything that it has queued
    int x = await SomeOtherFunction();
    WeShouldBeInTheMainThreadNow(); // ********* this should run in the main thread
}

async int SomeOtherFunction() {
    // Set a null SynchronizationContext because this function wants its continuations 
    // to run in the thread pool.
    SynchronizationContext prevContext = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext( null );

    try {

        // I want the continuation for this to be posted to a thread pool 
        // thread, not MyCustomContext.
        await Blah();

        WeShouldBeInAThreadPoolThread(); // ********* this should run in a thread pool thread

    } finally {
        // Restore the previous SetSynchronizationContext.
        SynchronizationContext.SetSynchronizationContext( prevContext );
    }
}

我得到的行为是每个等待之后的代码在看似随机的线程中执行。有时,WeShouldBeInTheMainThreadNow()在线程池线程中运行,有时候是主线程。有时WeShouldBeInAThreadPoolThread()正在运行

我在这里看不到一个模式,但我认为在您使用await的行中设置的SynchronizationContext.Current是将定义await之后的代码将执行的位置。这是一个不正确的假设吗?如果是这样,是否有一种紧凑的方式来做我想在这里做的事情?

2 个答案:

答案 0 :(得分:2)

我希望您的代码能够正常运行,但有几个可能的原因却不是:

  1. 确保SynchronizationContext在执行延续时保持最新状态。
  2. 在捕获SynchronizationContext时,未严格定义。
  3. SynchronizationContext中运行代码的常规方法是在一个方法中建立当前的方法,然后运行另一个(可能是异步的)依赖于它的方法。
  4. 避免当前SynchronizationContext的正常方法是将ConfigureAwait(false)附加到等待的所有任务中。

答案 1 :(得分:1)

关于await存在一种常见的误解,即以某种方式调用async - 实现的函数是专门处理的。

但是,await关键字对一个对象进行操作,它根本不关心等待对象的来源。

也就是说,您始终可以使用await Blah();

重写var blahTask = Blah(); await blahTask;

那么当您以这种方式重写外部await调用时会发生什么?

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

然后,还有另一个问题:内部方法的finally在延续中执行,这意味着它在线程池上执行 - 所以你不仅没有设置SynchronizationContext ,但是你的SynchronizationContext将来(可能)将在另一个线程上的某个时间恢复。但是,因为我并不真正理解SynchronizationContext的流动方式,很可能根本没有恢复SynchronizationContext,它只是在另一个线程上设置(请记住SynchronizationContext.Current 1}}是线程本地的......)

这两个问题相结合,很容易解释你观察到的随机性。 (也就是说,你正在从多个线程中操纵准全局状态......)

问题的根源是await关键字不允许安排延续任务。

一般情况下,您只需要指定&#34; await之后的代码与await&#34;之前的代码位于同一上下文中并不重要,在这种情况下,使用ConfigureAwait(false)是合适的;

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

但是,如果你绝对想要指定&#34;我希望await之后的代码在线程池上运行&#34; - 这应该是罕见的,然后你无法使用await,但你可以这样做,例如使用ContinueWith - 然而,您将混合使用Task个对象的多种方式,这可能会导致相当混乱的代码。

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