4

在异步函数内部使用与外部不同的 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() 正在运行

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

4

2 回答 2

2

我希望您的代码能够正常工作,但有几个可能的原因导致它不能工作:

  1. 确保您SynchronizationContext在执行其延续时是最新的。
  2. 捕获没有严格定义。SynchronizationContext
  3. 在 a 中运行代码的正常方法SynchronizationContext是建立当前的一对一方法,然后运行另一个依赖于它的(可能是异步的)方法。
  4. 避免当前的正常方法SynchronizationContext是附加ConfigureAwait(false)到所有等待的任务。
于 2014-10-07T12:04:01.963 回答
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;

然后,还有另一个问题:finallyfrom 内部方法在 continuation 中执行,这意味着它在线程池上执行 - 所以不仅你取消了你的设置SynchronizationContext,而且你的SynchronizationContext意志(可能)在某个时间恢复未来,在另一个线程上。但是,因为我不太了解它的SynchronizationContext流动方式,很可能SynchronizationContext根本没有恢复,它只是设置在另一个线程上(记住这SynchronizationContext.Current是线程本地的......)

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

问题的根源在于该await关键字不允许调度延续任务。

通常,您只需指定“后面的代码await与前面的代码在同一上下文中并不重要await”,在这种情况下,使用ConfigureAwait(false)将是合适的;

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

但是,如果您绝对想指定“我希望在await线程池上运行之后的代码”——这应该是很少见的,那么你不能用 来做到这一点await,但你可以用 来做到这一点ContinueWith——但是,你是将混合使用Task对象的多种方式,这可能会导致非常混乱的代码。

Task SomeOtherFunction() {
    return Blah()
        .ContinueWith(blahTask => WeShouldBeInAThreadPoolThread(),
                      TaskScheduler.Default);
}
于 2014-10-07T12:57:04.747 回答