Await在工作线程上继续,即使没有调用ConfigureAwait(false)

本文关键字:调用 ConfigureAwait false 工作 线程 继续 Await | 更新日期: 2023-09-27 18:29:49

我有一个正在等待UI线程的异步函数。我确信ConfigureAwait(false)没有被呼叫。事实上,我已经尝试过显式调用ConfigureAwait(true),只是为了确定。但是,当等待的Task完成时,它将在工作线程上继续。经过一番挖掘,我偶然发现了这个问题,它至少让我找到了一个可能的原因。我添加了

var context = SynchronizationContext.Current;
var scheduler = TaskScheduler.Current;

就在等待的函数之前,这样我就可以看到TaskAwaiter是否能够捕获上下文。上下文似乎为null,但调度程序被分配了一个有效的引用。然而,当等待的任务完成时,它仍在工作线程上继续。所以我又挖了一点。

使用Resharper,我在Task.SetContinuationForAwait:中发现了这个小宝石

// If the user wants the continuation to run on the current "context" if there is one...
if (continueOnCapturedContext)
{
    // First try getting the current synchronization context.
    // If the current context is really just the base SynchronizationContext type, 
    // which is intended to be equivalent to not having a current SynchronizationContext at all, 
    // then ignore it.  This helps with performance by avoiding unnecessary posts and queueing
    // of work items, but more so it ensures that if code happens to publish the default context 
    // as current, it won't prevent usage of a current task scheduler if there is one.
    var syncCtx = SynchronizationContext.CurrentNoFlow;
    if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))
    {
        tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, continuationAction, flowExecutionContext, ref stackMark);
    }
    else
    {
        // If there was no SynchronizationContext, then try for the current scheduler.
        // We only care about it if it's not the default.
        var scheduler = TaskScheduler.InternalCurrent;
        if (scheduler != null && scheduler != TaskScheduler.Default)
        {
            tc = new TaskSchedulerAwaitTaskContinuation(scheduler, continuationAction, flowExecutionContext, ref stackMark);
        }
    }
}

我在两个if语句上设置了断点,发现syncCtxscheduler都为null,所以这解释了为什么等待的任务在工作线程上继续,但没有解释为什么SynchronizationContext.Current在UI线程上为null,也没有解释为什么在即将调用继续时TaskScheduler.Current为null。

Await在工作线程上继续,即使没有调用ConfigureAwait(false)

事实证明,问题是由于试图使用包含来自本机Win32应用程序的WPF表单的类库造成的。普通的WinForms或WPF应用程序将在Control的构造函数中初始化SynchronizationContext。这通常发生在创建"停车"表单的过程中

由于我还不能解释的原因,无论创建了多少控件,SynchronizationContext都不会初始化。在创建任何表单之前手动创建它一段时间可以使awaited任务在正确的线程上继续。

if (SynchronizationContext.Current == null)
{
    AsyncOperationManager.SynchronizationContext = new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher);
}