何时使用TaskCreationOptions.LongRunning?

85

我一直想知道这个问题的答案,但从未真正找到过。

我知道这是任务计划程序在哪个任务上运行的提示,并且任务计划程序可以(或现在会?)为该任务实例化一个非线程池线程。

我不知道的是(令人惊讶的是,在互联网上找不到),何时将任务指定为长时间运行的“经验法则”。 是一秒钟吗? 30秒? 一分钟? 5分钟? 它是否与应用程序使用的任务数量有关? 作为程序员,我是否需要计算线程池中的线程数,创建的任务数,同时运行多少个长时间运行的任务,并基于此做出决策是否使用长时间运行的任务?

希望能够在这里学到一些东西。


嗯,在 Task 之前的世界中,当你需要在 ThreadPool.QueueUserWorkItemnew Thread 之间进行选择时,情况是一样的。 - Ivan Stoev
2
学习 这个这个这个。作为经验法则 - 除非任务确实需要长时间运行(与应用程序生存时间相同,接近于该时间或相对较长的任务),否则不要使用 LongRunning - Sinatr
没有具体的价值。请查看此线程:https://social.msdn.microsoft.com/Forums/en-US/8304b44f-0480-488c-93a4-ec419327183b/when-should-a-taks-be-considered-longrunning?forum=parallelextensions - Dennis
@Sinatr,好的,那么您会说如果应用程序运行24/7,运行一分钟的任务不应被视为长时间运行吗?或者这高度取决于应用程序启动了多少个这样的“1分钟运行任务”,潜在地同时启动了它们? - bas
4个回答

64

可以量化,当现有的线程池线程无法很快完成任务时,线程池管理器会添加一个额外的线程,每0.5秒执行一次,最多达到SetMaxThreads()设置的最大值,其默认值非常高。最佳数量是机器可用的处理器核心数量,通常为4个。运行超过可用核心的线程可能会因上下文切换开销而产生负面影响。

这是基于一个假设进行的,即这些现有的线程之所以没有进展,是因为它们没有执行足够的代码。换句话说,它们在I/O或锁上阻塞得太久了。因此,这些线程不能充分有效地利用核心,允许添加一个额外的线程来提高处理器使用率并完成更多的工作是完全适当的。

因此,当线程需要超过半秒钟才能完成时,它就是"长时间运行"。请记住,这是非常长的时间,在现代桌面级机器上大约相当于40亿个处理器指令。除非您正在运行计算重的代码(例如计算pi值到数十亿位数),从而实际执行那40亿个指令,否则实际的线程只有在阻塞得太久时才能达到这个时间。这是非常常见的,像数据库查询之类的东西通常会比较慢,并在工作线程上执行,并且占用很少的CPU。

否则,您需要验证线程池管理器的假设是否准确。任务应该需要很长时间,因为它没有有效地利用处理器。任务管理器是查看程序中处理器核心活动的简单方法,虽然它无法告诉您正在执行哪些代码。您需要使用单元测试来查看线程独立执行的情况。唯一完全准确的确定使用LongRunning是一个恰当的选择的方法是验证您的应用确实完成了更多的工作。


非常感谢您详细解答任务调度器的行为。此外,当创建额外线程时的决策逻辑真的很有帮助,并完美地解释了我们遇到的时间问题(执行任务的延迟1-2秒)。 - bas
2
所有的I/O不都应该是异步的吗?为什么要在等待I/O时阻塞线程?这是为了适应早期的非异步/await世界而设计的吗? - Michael Parker
1
呃,很少有程序员会将他们的文件处理代码异步化,磁盘驱动器已经足够快了。除非像在WinRT中一样被迫这样做。 - Hans Passant

22

我对Hans的答案基本上是同意的,以下是我的修改。

指定LongRunning最重要的原因是为了获得几乎保证和立即执行。不需要等待线程池为您的工作项分配线程。我说“几乎”是因为操作系统可以选择不调度您的线程。但您将获得一些CPU份额,并且通常不需要很长时间才能发生。

通过指定LongRunning,您可以跳过队列前面的等待。如果线程池处于负载下,无需等待每秒发出2个线程。

因此,您会在必须以及时稳定的方式而不一定是最有效的方式运行的内容中使用LongRunning。例如,某些UI工作、游戏循环、进度报告等。

启动和停止线程的成本大约为1毫秒的CPU时间。这远高于发出线程池工作项的成本。我刚刚进行了一个基准测试,每秒发出和完成3M个项目。该基准测试相当人为,但数量级正确。

LongRunning被记录为提示,但实际上非常有效。没有启发式算法考虑您给出的提示。它被假定为正确的。


2
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - bas

3

何时将任务指定为长时间运行

这取决于任务正在做什么。如果任务包含 while(true) {...} 并一直存在直到应用程序关闭,那么指定 LongRunning 就有意义。如果您创建任务来排队某些操作并防止阻塞当前线程,则不需要关心(不要指定任何内容)。

这取决于其他任务在做什么。使用或不使用 LongRunning 运行几个任务并不重要。但是,如果创建成千上万个任务,每个任务都要求新线程,则可能会出现问题。或者相反,如果不指定它,您可能会遇到 线程饥饿

一个简单的想法是:你是否希望新任务在新线程中运行,还是不关心?如果是前者-则使用 LongRunningOption。这 并不意味着任务将在另一个线程中运行,只是在必须指定它时的一个好标准。

例如,当使用ContinueWith时,LongRunningExecuteSynchronously相反(有一个检查来防止两者同时被指定)。如果您有多个连续体,那么也许您想要避免队列的开销,并在同一线程或相反的线程中运行特定的连续体-您不希望其中一个连续体干扰其他连续体,然后您可以专门使用LongRunning。请参考本文(和本文)了解ExecuteSynchronously

2
一个长时间运行的任务可能会进入等待状态,在运行的线程上阻塞,或者需要太多的 CPU 时间(我们将在后面讨论这一点)。
有些人可能认为这个定义过于宽泛,很多任务都会长时间运行,但是想一想,即使等待时间被限制在很短的超时时间内,该任务仍然没有有效地利用 CPU。如果这些任务的数量增加,你会发现它们在 MinWorkerThreads(请参见ThreadPool.SetMinThreads)之后不会呈线性比例增长,性能下降得非常严重。
解决方法是将所有 I/O(文件、网络、数据库等)都改为异步方式。
还有一些长时间运行的任务是由于长时间的 CPU 密集型计算而导致的。
解决方法是推迟计算,例如在某些点插入 await Task.Yield(),或者更好的方法是明确地延迟计算,通过安排一个接一个的任务来处理先前分割的数据块或在有界时间限制内处理缓冲区。
“太长时间”由您自己决定。
当你处于共享线程池的环境下时,任何时间都太长了,你必须选择一个合理的值。例如,在IIS下的ASP.NET中,查看最常见请求每个请求所花费的平均时间。同样,在使用线程池处理消息队列的服务中,需要以每条消息的平均时间为标准。
更一般地说,“太长的时间”是指工作项排队的速度快于处理的速度。可能会有工作负载突发情况,因此您应该将其平均分配到您关心的时间单位上,比如一秒钟、一分钟、十分钟等。当您有SLA时,应该在某个地方定义这个间隔。
在确定了一个合理的值之后,必须在实践中验证是否可以增加或减少它。通常来说,如果可以增加它,除非您能看到显著的性能差异,否则最好不要增加它。“显著”意味着处理的项目数量呈现超线性增长,因此如果是线性的(或低于线性的,这种情况也可能发生),就不要这么做。
根据我的经验,如果你有一个长时间运行的任务,通常最好自己管理线程或一组线程。

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