【问题标题】:Await continuing on worker thread even with no call to ConfigureAwait(false)即使没有调用 ConfigureAwait(false),也等待在工作线程上继续
【发布时间】:2016-01-11 23:01:35
【问题描述】:

我有一个正在等待 UI 线程的异步函数。我确定ConfigureAwait(false) 没有被调用。事实上,为了确定,我已经尝试明确调用ConfigureAwait(true)。但是,当等待的任务完成时,它会在工作线程上继续。经过一番挖掘,我偶然发现了this 问题,这至少为我指出了一个可能的原因。我加了

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

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

使用 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 都为空,所以这解释了为什么等待的任务在工作线程上继续,但它没有解释为什么 SynchronizationContext.Current 为空在 UI 线程上或为什么 TaskScheduler.Current 在将要调用延续时为空。

【问题讨论】:

  • 很可能你在等待之前没有在 UI 线程上运行。 Thread.CurrentThread.IsThreadPoolThread 的值是多少?
  • 它是false,托管 ID 为 1,与“线程”窗口中列出的主线程匹配。我确定等待是从 UI 线程进行的。
  • 你能用一个小例子重现这个问题吗?
  • 不幸的是,没有。我尝试在一次性应用程序中重现该问题,但到目前为止还没有成功。
  • 发生这种情况时 SynchronizationContext.Current 的类型是什么? TaskScheduler.Current 是什么?也许您可以通过删除代码来重现,直到问题消失。

标签: c# multithreading asynchronous


【解决方案1】:

原来问题是由于尝试使用包含来自本机 Win32 应用程序的 WPF 表单的类库造成的。普通的 WinForms 或 WPF 应用程序会在 Control 的构造函数中初始化 SynchronizationContext。这通常会在创建“停车”表单期间发生。

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

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

【讨论】:

  • 我相信 WPF 只在其主循环(调度程序的 pushframe 或其他)中安装其 SynchronizationContext。这对我来说比 WinForm 将其 SyncCtx 安装在碰巧构造 UI 句柄的任何线程上的习惯更有意义。
猜你喜欢
  • 2015-11-19
  • 1970-01-01
  • 1970-01-01
  • 2013-12-18
  • 2017-03-29
  • 1970-01-01
  • 1970-01-01
  • 2013-04-25
相关资源
最近更新 更多