TPL 可以在多个线程上运行任务吗?

Posted

技术标签:

【中文标题】TPL 可以在多个线程上运行任务吗?【英文标题】:Can the TPL run the Task on more than one thread? 【发布时间】:2014-09-17 12:51:31 【问题描述】:

欢迎提供特定于 Mono/Xamarin 的答案。

我正在使用 Task.Run() 运行 System.Threading.Tasks。 TPL 会在任务执行的整个生命周期内将创建的任务分配给单个线程吗?或者是否有可能创建的任务在运行时会被抢占,然后在不同的线程上再次调度?

Thread.CurrentThread.ManagedThreadId 会在任务的生命周期内保持不变吗?

对于长时间运行的任务,答案是否不同?

有没有办法控制这方面的 TPL 行为?

【问题讨论】:

【参考方案1】:

TPL 会在任务执行的整个生命周期内将创建的任务分配给单个线程吗?

指导原则是同步部分工作在单个线程上运行。因此,如果您将同步委托传递给Task.Run,它将全部在单个线程上运行:

await Task.Run(() => Thread.Sleep(5000)); // same thread after sleep

但是,如果您有异步代码,则每个await 都是代码中暂停该方法的位置。当方法恢复时,它将在线程池线程(可能是不同的线程)上恢复。

await Task.Run(async () => await Task.Delay(5000)); // thread may change

长时间运行标志(不能传递给Task.Run)不会影响此行为,因为它仅适用于第一个同步部分。

控制这种情况的正常方法是使用自定义TaskSchedulerSynchronizationContext。但是,在走这条路之前,您可能需要考虑另一种方法。应该有比将方法强制回到同一个线程更好的解决方案。例如,线程仿射同步原语都有async兼容的等价物,线程本地存储可以替换为闭包/类字段/逻辑调用上下文等。

【讨论】:

值得注意的是,从技术上讲,在您的第二个示例中,Run 仍在执行它在单个线程上接收到的全部委托。正是您传递给Run 的委托将其行为明确定义为做某事,然后调度要在另一个线程池线程中执行的操作,所以说Run 将只执行委托是完全正确的您可以在单个线程上执行它,尽管各种其他类型的任务可以表示在多个线程上执行的操作。 我相信你和 Servy 是正确的,而我能得出的最接近“证明”的是,如果同步委托在更多设备上运行,C# 锁 (obj) 构造将无法正常工作不止一个线程。这在任何地方都有记录吗? async(其中await 是“屈服点”)的基本概念是documented here。 Task.Run async/sync 委托在 Stephen Toub's blog 上进行了简要讨论。【参考方案2】:

Task.Run 将调度委托在线程池中执行,并且线程池只会在单个线程上执行给定的委托。它不会在线程之间移动它。

也就是说,Task 在概念上只是表示在未来某个时间点完成某些异步操作。它可以代表最终完成的任何事情。如果你想创建一个任务来表示一个委托在一个线程上的执行,然后另一个委托在另一个线程上的执行,那么你绝对可以做到。

许多使用async 关键字的方法实际上可以做到这一点。因为,在没有设置同步上下文的应用程序中,由于await 调用而连接起来的延续可能(有时可以优化)每个都在线程池中单独调度。例如,在控制台应用程序中运行的以下代码会生成一个任务,该任务可能会打印出三个完全不同的数字。 (当然,它们并不是要求不同的;线程池可能只是碰巧将延续安排在同一个线程上运行。)

public static async Task Foo()

    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    await Task.Delay(100);
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    await Task.Delay(100);
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

【讨论】:

您如何确定地知道这一点:“它不会在线程之间移动它。”? 嗯,首先,它不可能。当您告诉线程执行委托时,没有机制可以只执行一半然后停止,无需合作。逻辑 C# 线程可能没有与 OS 线程的 1:1 映射(尽管我很有信心这实际上不会发生),但这超出了您确实需要了解的抽象层。如果它可能改变执行你的代码的线程(它不能)那不应该破坏你的程序。如果是这样,你几乎肯定会做一些你不应该做的事情。 所以基本上,你不确定(没关系)。我不知道 TPL 的实现,但我可以想象一个长时间运行的任务可以被抢占,然后由于线程数量有限而在稍后继续。我可能猜想 TPL 可以选择不同的线程来继续执行任务。至于你在不了解任何上下文的情况下说出“你几乎肯定会做你不应该做的事情”的能力,伙计,这太棒了。您应该找到一种通过该技能获利的方法。 @jeff7091 我知道线程池不会在多个线程上执行给定的操作。线程一直暂停并稍后继续,但它们不会暂停然后执行另一个线程的状态。是的,即使不知道上下文,我也可以非常确信,如果您在多线程环境中编写代码并期望它在特定线程上执行,那么您的可能性非常正在做一些你不应该做的事情,因为使用这些抽象层的全部目的是抽象出这些概念。 @jeff7091 我理解你的想法。可能无法找到 TPL 不会跨线程移动运行代码的事实。理论上,它可以通过运行时的合作来做到这一点。另一方面:a)几乎不需要这样做(您不同意吗?) b)很难实现 c)这将被用户代码检测到,因为 ManagedThreadId 可以随时更改 d)非托管代​​码不能t 处理这个问题,.NET 一直调用非托管代码。 => TPL 不这样做。我无法为您提供官方消息来源。资料来源:Servy 和我。

以上是关于TPL 可以在多个线程上运行任务吗?的主要内容,如果未能解决你的问题,请参考以下文章

C# 多线程 ,可以多个线程做一任务,并且提高做任务的效率吗

使用 TPL 在多个不同线程上创建单例对象

不在 TPL Task 对象上调用 Dispose() 是不是可以接受?

具有永久任务/线程的 TPL 数据流块

如何为 TPL 中的任务分配名称

你了解Java多线程吗Java多线程技能