【问题标题】:Why doesn't my Task.ContinueWith execute in .NET 4.5?为什么我的 Task.ContinueWith 不在 .NET 4.5 中执行?
【发布时间】:2015-05-19 06:08:31
【问题描述】:

考虑下面的代码

Task<T>.Factory.StartNew(() => 
    {
      // block #1: load some data from local file cache
    }
  )
  .ContinueWith(task => 
    {
      // block #2: handle success or failure of load-from-cache operation and surface to application
    }, 
    cancellationToken,
    TaskContinuationOptions.NotOnCanceled,
    TaskScheduler.FromCurrentSynchronizationContext()
  )
  .ContinueWith(task => 
    {
      // block #3: load data from remote data source
    },
    TaskContinuationOptions.NotOnCanceled
  );

在 .NET 4.0 中,此代码按预期执行:第一个块在后台线程中运行,然后第二个块运行,最后第三个块运行。

然而,在 .NET 4.5 中,第二个块永远不会运行,无论第一个块发生什么(成功、错误或取消)。第三块也不运行,等待未启动的第二块。

应用背景

此代码位于 WPF 应用程序中。它在应用程序初始化期间运行,加载应用程序启动所需的一些数据。在主线程(我从中调用此异步代码)上,我正在等待从代码块 #3 填充结果,然后再继续。如果远程数据调用超时,初始化将继续使用来自块 #1(缓存)的数据。

我尝试过的事情

  1. 将 TaskScheduler.FromCurrentSynchronizationContext() 添加到 StartNew 调用。这会导致 StartNew lambda 中的代码永远不会执行(即使它执行了,我也不希望它在主线程上运行)。
  2. 从 ContinueWith 中删除 TaskScheduler.FromCurrentSynchronizationContext()(并保留 cancelToken 或 TaskContinuationOptions 但不能同时保留,因为没有重载仅支持这两个参数)。这似乎有效(代码执行),但我担心副作用,因为我不确定为什么它有效。

【问题讨论】:

  • 听起来你阻塞了 UI 线程,所以无法向它发布延续。
  • 您能否简要介绍一下您正在开发的应用程序(Web、Windows、WPF 等)?以及您要在第 1 块中实现的功能是什么?
  • @vendettamit 添加了背景和代码意图。希望这会有所帮助。
  • UI 挂起了吗?在手头或您希望任务运行期间暂停调试器。堆栈上有什么?
  • “在主线程(我从中调用此异步代码)上,我正在等待从代码块 #3 填充结果,然后再继续。” -- 是的,Servy 是对的。您希望块 #2 在 UI 线程上运行,但您在块 #3 完成之前阻止 UI 线程,并且块 #3 仅在块 #2 之后运行。那永远行不通。为什么需要阻塞 UI 线程?如果您还不想运行其余的初始化,您能否不简单地将其余的初始化移动到块 #3 之后运行的延续?

标签: c# .net asynchronous .net-4.5


【解决方案1】:

这两个版本的.Net中ContinueWith和TaskScheduler的属性Current和Default的设计存在问题

在 .Net 4.0 中,TaskScheduler 的 Current 和 Default 都具有相同的值,即 ThreadPoolTask​​Scheduler,它是 ThreadPool 的上下文调度器,它不是更新 UI 的调度器,即 SynchronizationContextTaskScheduler这就是您的代码在 .Net 4.0 中运行良好的原因。

在 .Net 4.5 中,情况发生了变化。所以当你说 TaskScheduler.Current 和 TaskScheduler.Default 那么你会得到两个不同的调度程序(在你的情况下是 WPF)

当前是 = SynchronizationContextTaskScheduler

默认为 = ThreadPoolTask​​Scheduler

现在回到您的问题,当您使用 ContinueWith 选项时,调度程序的硬编码值为 TaskScheduler.Current。特别是在 WPF 和 Asp.net SynchronizationContextTaskScheduler 中意味着它是 UI 线程同步上下文,一旦它被阻塞,其他任何东西都不会执行与之关联,直到当前正在执行的线程完成,在 UI 线程上下文中运行。

建议(.Net 4.5): 尝试在 ContiueWith 中传递 TaskScheduler.Default(NON UI Scheduler) 或避免使用 ContinueWith,而是以排队方式加入任务。

我希望这能让您清楚地了解行为发生变化的原因。有关更多详细信息,请参阅此讨论:Why is TaskScheduler.Current the default TaskScheduler?

【讨论】:

  • 如果您投反对票,请发表评论,以便我知道我的理解错在哪里。
  • 传入TaskScheduler.Default 而不是TaskScheduler.FromCurrentSynchronizationContext() 为我解决了这个问题。应用程序中的其他一些地方遵循相同的模式,不再更新 UI 中的内容,但这有助于我理解原因,并使这些更容易修复。谢谢,@vendettamit。
猜你喜欢
  • 2015-01-27
  • 2012-09-19
  • 1970-01-01
  • 2019-04-11
  • 2010-10-08
  • 2013-02-22
  • 1970-01-01
  • 2017-06-19
  • 2015-11-02
相关资源
最近更新 更多