如何使任务不在UI线程上执行

15

以下代码是真实应用程序中一个代码的简化版本。下面的问题是,一个长时间的工作将在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上下文中调用的。ShortUIWork也是如此,因为它在TaskScheduler中被定义。然而,LongWork最终也会在UI线程中被调用,这当然会阻塞UI。

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


tasks 创建的线程根据定义 不会 在 UI 上运行。您确定 LongWork 阻塞了 UI 线程吗? - Tigran
2
@Tigran:TPL 默认使用后台线程,但并非定义。 - user180326
1
没有能够重现问题的代码是没有帮助的。这种行为的常见原因是COM组件的包装类。COM会保留那些声称不支持任何类型线程安全的类的对象,通过自动将任何调用传回到创建它们的线程来实现。 - Hans Passant
代码对我来说可以重现这个问题。 - Kent Boogaart
1个回答

11

LongRunning只是对TaskScheduler的提示。在使用TaskScheduler.FromCurrentSynchronizationContext()返回的SynchronizationContextTaskScheduler中,它似乎会忽略这个提示。

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

LongRunning - 指定任务是一个长时间运行、粗粒度操作。它为 TaskScheduler 提供了一个提示,表明可能需要超额提交。

由于 UI 线程不是线程池线程,因此不会发生“过度订阅”(线程池饥饿),因此在 SynchronizationContextTaskScheduler 中这个提示没有效果也就有些合理。

无论如何,你可以通过切换回默认的任务调度程序来解决这个问题:

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

为什么在UI线程上执行时,LongWork中的Debug.Assert(this.Dispatcher.CheckAccess() == false);会通过呢? - Branko Dimitrijevic
@Branko:我不明白。如果按照我的答案更改代码,LongWork就不会在UI线程上执行。这就是为什么断言通过的原因(但在原始代码中失败)。 - Kent Boogaart
奇怪,我本来以为在我最初调试它时已经通过了,但现在(在原始代码中)它失败了。要么是我遇到了一些奇怪的调试器交互(无论那是什么),要么就是我的大脑出了问题;)对此我深表歉意。 - Branko Dimitrijevic
谢谢,使用TaskScheduler.Default确实解决了问题。然而,这有点令人担忧。基本上,如果你在使用WPF,你应该对所有不想在UI线程中运行的工作使用TaskScheduler.Default,以确保你的任务不会意外地阻塞UI线程。 - Tomba
1
@Tomba:我认为你的用例有一点不寻常。通常情况下,您只需要使用默认的任务调度程序来完成工作,然后在工作完成后切换回UI任务调度程序。如果您需要在UI线程上执行少量工作,为什么要使用任务来完成呢?您可以直接将其内联处理,然后启动长时间运行的任务。 - Kent Boogaart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接