2016-04-14 115 views
3

在处理任务时,经验法则似乎是线程池 - 通常由例如调用Task.Run()Parallel.Invoke() - 应该用于相对较短的操作。在处理长时间运行的操作时,我们应该使用​​标志,以便 - 据我所知 - 避免堵塞线程池队列,即将工作推送到新创建的线程。何时应该将任务视为“长时间运行”?

但究竟是一个长时间运行操作? 就时间而言,漫长多久?在决定是否使用LongRunning时,除了期望的任务持续时间之外是否还有其他因素需要考虑,如预期的CPU架构(频率,内核数量等)或将尝试执行的任务数量从程序员的角度立即运行?

例如,假设我有500个任务要在专用应用程序中处理,每个任务需要10-20秒才能完成。我是否应该使用Task.Run(例如,在一个循环中)启动所有500个任务,然后等待它们全部,也许是LongRunning,而保留默认的最大并发级别?然后,如果在这种情况下设置了LongRunning,那么与创建LongRunning相比,这是否不会创建500个新线程,并且实际上会导致大量开销和更高的内存使用量(由于分配了额外的线程)?假设在这500个等待期间没有新任务将被安排执行。

我猜想决定设置LongRunning取决于在给定的时间间隔内对线程池的请求数量,而LongRunning应该只用于预计需要大大延长的任务线程池放置的任务 - 根据定义,最多只占所有任务的一小部分。换句话说,这似乎是一个排队和线程池利用率优化问题,应该可能通过测试逐案解决,如果有的话。我对么?

回答

5

这种无所谓。问题不在于时间问题,而在于你的代码在做什么。如果您正在执行异步I/O,那么您仅在单个请求之间的短时间内使用该线程。如果你在做CPU工作......那么你正在使用CPU。没有“线程池饥饿”,因为CPU被充分利用。

真正的问题是,当你在做阻塞工作时,并不是使用CPU。在这种情况下,线程池饥饿会导致CPU利用率不足 - 你说“我需要CPU来完成我的工作”,然后你实际上并没有使用它。

如果您不使用阻塞API,那么LongRunning使用Task.Run没有意义。如果你不得不异步运行一些传统的阻止代码,使用LongRunning可能是一个好主意。总的工作时间并不像“你多久这样做”这么重要。如果您基于用户单击GUI旋转了一个线程,与首先单击按钮的操作中包含的所有延迟相比,成本非常微小,您可以使用LongRunning就可以避免线程 - 池。如果你正在运行一个产生大量阻塞任务的循环,请停止这样做。这是一个坏主意:D

例如,假设没有异步API备选File.Exists。所以如果你发现这样会给你带来麻烦(例如,在网络连接错误的情况下),你可以使用Task.Run来启动它 - 因为你没有在做CPU工作,所以你会使用LongRunning

相比之下,如果您需要做一些基本上100%CPU工作的图像处理,那么操作需要多长时间并不重要 - 这不是LongRunning的事情。

最后,使用LongRunning最常见的情况是当你的“工作”实际上是老派“循环,并定期检查是否应该做某件事,然后再做循环”。长时间运行,但99%的时间阻止了一些等待处理或类似的东西。同样,这只在处理不受CPU限制的代码时才有用,但它没有适当的异步API。例如,如果您需要编写自己的SynchronizationContext,您可能会发现这样的情况。

现在,我们该如何将它应用到您的示例中?那么,我们不能,没有更多的信息。如果你的代码是CPU限制的,Parallel.For和朋友是你想要的 - 这些确保你只使用足够的线程来确定CPU的容量,并且为它使用线程池是很好的。如果它是而不是 CPU绑定...除了使用LongRunning以外,如果要并行运行任务,则没有其他选项。理想情况下,这样的工作将由您可以安全地调用的异步调用以及您自己的线程中的异步调用组成。

+0

谢谢你的详细解答。是的,我正在承担CPU绑定任务。但是,在IO界限任务的情况下,为什么“LongRunning”与_parallelism_有什么关系? 'LongRunning = false'不会产生500个IO-bound任务,只是将它们放在线程池中(假设有足够的空间),而'LongRunning = true'会产生500个新线程 - 感知的并发性和响应性接近相同给用户,还是由于额外的线程创建开销在后一种情况下更糟? – w128

+0

@ w128这是关于线程池如何分配新线程的。默认情况下,它根据CPU核心数量(通常约为物理核心数量的两倍)进行平衡,这对CPU的工作非常有用。当你需要*多*个线程时,线程池根据需要分配它们,但是延迟 - IIRC需要大约2s才能将新线程添加到池中。因此,如果一次向线程池添加500个阻塞任务,则池至少需要1000s才能拥有足够的线程来同时处理它们。 “LongRunning”只受旋转起来的限制,速度要快得多。 – Luaan

2

你不应该在你的情况下使用​​。我会用Parallel.For

LongRunning选项是不会被使用,如果你要创建大量的任务,就像你的情况。它将用于创建将长时间运行的几个任务。

顺便说一句,我从来没有在任何类似的情况下使用此选项。

1

正如你所指出的,​​的目的是

允许线程池来继续处理,即使有一个任务正在运行的长时间

至于工作项目何时使用它:

它本身并不是一个特定的长度......如果你通过性能测试发现,你通常只使用LongRunning,而不是使用它是因为在处理其他工作方面长期拖延。

Source

2

当使用任务,将出现一个经验法则是,该线程池 - 通常由例如调用Task.Run()或Parallel.Invoke() - 应该用于相对较短的操作。在处理长时间运行的操作时,我们应该设置TaskCreationOptions.LongRunning为true,以便 - 据我所知 - 避免堵塞线程池队列,即将工作推送到新创建的线程。

绝大多数的时候,你不需要使用LongRunning可言,因为线程池将调整2秒后,以“丢失”一个线程长时间运行操作。

LongRunning的主要问题是它迫使您使用very dangerous StartNew API

换句话说,这似乎是一个排队和线程池利用率优化问题,应该可能通过测试逐案解决,如果有的话。我对么?

是的。第一次写代码时,你永远不应该设置LongRunning。如果由于线程池注入速率而看到延迟,那么您可以仔细地加LongRunning

相关问题