在处理任务时,一条经验法则似乎是线程池——通常由例如调用 Task.Run()
或 Parallel.Invoke()
- 应该用于相对较短的操作。在处理长时间运行的操作时,我们应该使用 TaskCreationOptions.LongRunning
标志,以便 - 据我所知 - 避免阻塞线程池队列,即将工作推送到新的 -创建线程。
但是长时间运行操作到底是什么? 在时间方面有多长?在决定是否使用 LongRunning
时,除了预期的任务持续时间之外,是否还有其他因素需要考虑,例如预期的 CPU从程序员的角度来看,体系结构(频率、内核数量...)或将尝试同时运行的任务数量?
例如,假设我有 500 个任务要在专用应用程序中处理,每个任务需要 10-20 秒才能完成。我是否应该使用 Task.Run(例如在循环中)启动所有 500 个任务,然后等待所有任务,也许作为 LongRunning
,同时保留默认的最大并发级别?再一次,如果我在这种情况下设置 LongRunning
,与省略 长时间运行
?这是假设在等待这 500 个任务时不会安排执行新任务。
我猜测设置 LongRunning
的决定取决于在给定时间间隔内对线程池发出的请求数,并且 LongRunning
应该只被使用对于预计比大多数线程池放置的任务花费更长的时间的任务——根据定义,最多占所有任务的一小部分。换句话说,这似乎是一个队列和线程池利用优化问题,如果有的话,应该可以通过测试逐个解决。我说得对吗?
最佳答案
这有点无关紧要。问题不在于时间,而在于你的代码在做什么。如果您正在执行异步 I/O,那么您只是在各个请求之间的短时间内使用线程。如果你在做 CPU 工作……好吧,你正在使用 CPU。不存在“线程池饥饿”,因为 CPU 已得到充分利用。
真正的问题是当您进行不不使用 CPU 的阻塞工作时。在这种情况下,线程池饥饿会导致 CPU 利用率不足 - 你说“我的工作需要 CPU”,但你实际上并没有使用它。
如果您不使用阻塞 API,则将 Task.Run
与 LongRunning
一起使用是没有意义的。如果您必须异步运行一些遗留阻塞代码,使用 LongRunning
可能是个好主意。总工作时间并不像“你做这件事的频率”那么重要。如果您根据用户点击 GUI 启动一个线程,与首先点击按钮的行为中已经包含的所有延迟相比,成本很小,您可以使用 LongRunning
避免线程池就好了。如果您正在运行一个产生大量阻塞任务的循环……停止这样做。这是个坏主意 :D
例如,假设没有异步 API 替代方案 File.Exists
。因此,如果您发现这给您带来了麻烦(例如,通过错误的网络连接),您可以使用 Task.Run
启动它 - 因为您没有执行 CPU 工作,所以您会使用 LongRunning
。
相比之下,如果您需要进行一些基本上 100% CPU 工作的图像处理,则操作需要多长时间并不重要 - 这不是 LongRunning
事情。
最后,使用 LongRunning
的最常见场景是当您的“工作”实际上是老式的“循环并定期检查是否应该完成某些事情,执行它然后再次循环” .长时间运行,但 99% 的时间只是阻塞在某个等待句柄或类似的东西上。同样,这仅在处理不受 CPU 限制但没有适当的异步 API 的代码时才有用。例如,如果您需要编写自己的 SynchronizationContext
,您可能会发现类似的内容。
现在,我们如何将其应用到您的示例中?好吧,我们不能,不能没有更多信息。如果您的代码受 CPU 限制,Parallel.For
和 friend 就是您想要的 - 这些确保您只使用足够的线程来使 CPU 饱和,为此使用线程池很好。如果它不受 CPU 限制...如果您想并行运行这些任务,除了使用LongRunning
之外您别无选择。理想情况下,这样的工作将包括您可以安全调用的异步调用和从您自己的线程中 await Task.WhenAll(...)
。
关于c# - 什么时候应该将任务视为 "long running"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36626452/