等待不使用当前的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()正在运行
我没有在这里看到一个模式,但我认为无论SynchronizationContext.Current设置在您使用await的行是什么,它将定义await后面的代码将执行的位置。 这是一个不正确的假设吗? 如果是这样,是否有一种紧凑的方式来做我想在这里做的事情?
关于await
有一个常见的误解,即以某种方式调用async
-implemented函数是专门处理的。
但是, await
关键字对一个对象进行操作,它根本不关心等待对象的来源。
也就是说,你总是可以重写await Blah();
with var blahTask = Blah(); await blahTask;
var blahTask = Blah(); await blahTask;
那么当你以这种方式重写外部await
调用时会发生什么?
// Synchronization Context leads to main thread; Task xTask = SomeOtherFunction(); // Synchronization Context has already been set // to null by SomeOtherFunction! int x = await xTask;
然后,还有另一个问题:内部方法的finally
在延续中执行,这意味着它在线程池上执行 – 所以不仅你没有设置SynchronizationContext
,而且你的SynchronizationContext
将(可能)恢复在未来的某个时间,在另一个线程上。 但是,因为我不太了解SynchronizationContext
的流动方式,所以很可能根本没有恢复SynchronizationContext
,它只是在另一个线程上设置(请记住SynchronizationContext.Current
是线程本地的… )
这两个问题相结合,很容易解释您观察到的随机性。 (也就是说,你正在从多个线程中操纵准全局状态……)
问题的根源是await
关键字不允许调度continuation任务。
通常,您只需要指定“ await
之后代码与await
之前的代码在同一上下文中并不重要”,在这种情况下,使用ConfigureAwait(false)
是合适的;
async Task SomeOtherFunction() { await Blah().ConfigureAwait(false); }
但是,如果你绝对想要指定“我希望await
之后的代码在线程池上运行” – 这应该是罕见的 ,那么你不能await
,但你可以用例如ContinueWith
来做 – 但是,您将混合使用Task
对象的多种方式,这可能会导致相当混乱的代码。
Task SomeOtherFunction() { return Blah() .ContinueWith(blahTask => WeShouldBeInAThreadPoolThread(), TaskScheduler.Default); }
我希望你的代码可以工作,但有几个可能的原因不是:
- 确保
SynchronizationContext
在执行其continuation时是最新的。 - 捕获
SynchronizationContext
时没有严格定义。 - 在
SynchronizationContext
运行代码的常规方法是在一个方法中建立当前的一个,然后运行依赖于它的另一个(可能是异步的)方法。 - 避免当前
SynchronizationContext
的常规方法是将ConfigureAwait(false)
附加到等待的所有任务。