wait不使用当前SynchronizationContext

本文关键字:SynchronizationContext wait | 更新日期: 2023-09-27 18:25:42

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

我的程序的大多数代码都使用自定义的SynchronizationContext,它只是将SendOrPostCallback排队,并在我的主线程中的特定已知点调用它们。我在一开始就设置了这个自定义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后面的代码将在哪里执行。这是一个错误的假设吗?如果是的话,有没有一种紧凑的方式来做我在这里要做的事情?

wait不使用当前SynchronizationContext

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

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

关于await有一个常见的误解,认为调用async实现的函数是经过特殊处理的。

然而,await关键字对对象进行了操作,它根本不关心不可用对象的来源。

也就是说,你总是可以用var blahTask = Blah(); await blahTask; 重写await Blah();

那么,当您以这种方式重写外部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是在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);
}