【问题标题】:How to get a task NOT to be executed on the UI thread如何获取不在 UI 线程上执行的任务
【发布时间】:2012-03-17 19:31:40
【问题描述】:

以下代码是实际应用程序中代码的简化。下面的问题是会在 UI 线程中运行长时间的工作,而不是在后台线程中运行。

    void Do()
    {
        Debug.Assert(this.Dispatcher.CheckAccess() == true);
        Task.Factory.StartNew(ShortUIWork, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
    }

    void ShortUIWork()
    {
        Debug.Assert(this.Dispatcher.CheckAccess() == true);
        Task.Factory.StartNew(LongWork, TaskCreationOptions.LongRunning);
    }

    void LongWork()
    {
        Debug.Assert(this.Dispatcher.CheckAccess() == false);
        Thread.Sleep(1000);
    }

所以 Do() 通常从 UI 上下文中调用。由 TaskScheduler 定义的 ShortUIWork 也是如此。但是,LongWork 最终也会在 UI 线程中调用,这当然会阻塞 UI。

如何保证任务不在UI线程中运行?

【问题讨论】:

  • tasks 根据定义创建一个不会在 UI 上运行的线程。你确定LongWork 会阻塞 UI 线程吗?
  • @Tigran:TPL 默认使用后台线程,而不是根据定义。
  • 不能重现问题的代码无济于事。出现这种行为的一个常见原因是 COM 组件的包装类。 COM 通过自动将任何调用编组回创建它们的线程来保持类的对象不支持任何类型的线程安全。
  • 代码为我重现了这个问题。

标签: c# wpf task-parallel-library


【解决方案1】:

LongRunning 只是对TaskScheduler 的提示。对于SynchronizationContextTaskScheduler(由TaskScheduler.FromCurrentSynchronizationContext() 返回),它显然忽略了提示。

一方面,这似乎违反直觉。毕竟,如果任务长时间运行,您不太可能希望它在 UI 线程上运行。另一方面,根据 MSDN:

LongRunning - 指定任务将长期运行, 粗粒度操作。它为 TaskScheduler 提供了一个提示: 可能需要超额认购。

由于 UI 线程不是线程池线程,因此不会发生“超额订阅”(线程池饥饿),因此提示对SynchronizationContextTaskScheduler 无效是有道理的。

无论如何,您都可以通过切换回默认任务计划程序来解决此问题:

void ShortUIWork()
{
    Debug.Assert(this.Dispatcher.CheckAccess() == true);
    Task.Factory.StartNew(LongWork, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}

【讨论】:

  • 为什么LongWork 中的Debug.Assert(this.Dispatcher.CheckAccess() == false); 会通过,即使它是在 UI 线程上执行的?
  • @Branko:我不关注。如果根据我的回答更改了代码,LongWork 不会在 UI 线程上执行。这就是断言通过(但原始代码失败)的原因。
  • 奇怪,我可以发誓,当我最初调试它时它通过了,但现在它失败了(在原始代码中)。要么我遇到了一些奇怪的调试器交互(无论是什么),要么这只是我的脑力衰竭;)我很抱歉。
  • 谢谢,使用 TaskScheduler.Default 确实解决了这个问题。不过,这有点令人担忧。基本上,如果您使用 WPF,您应该对所有不想在 UI 线程中运行的工作使用 TaskScheduler.Default,以确保您的 Task 不会意外阻塞 UI 线程。
  • @Tomba:我认为您的用例有点不寻常。通常,您只需使用默认任务调度程序来完成工作,然后在工作完成后切换回 UI 任务调度程序。如果您首先要在 UI 线程上完成少量工作,那么为什么要使用任务来完成这项工作呢?您可以改为内联执行,然后启动长期运行的任务。
猜你喜欢
  • 2013-04-08
  • 2016-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多