应该何时将任务视为“长时间运行”?

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

但究竟什么是长期运行从时间上看,多长时间? 在决定是否使用LongRunning ,是否还要考虑除预期任务持续时间之外的其他因素,例如预期的CPU架构(频率,核心数……)或将尝试的任务数量从程序员的角度立刻运行?

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

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

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

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

如果您没有使用阻止API,那么使用带有LongRunning是没有意义的。 如果必须异步运行一些传统的阻塞代码,使用LongRunning可能是个好主意。 总工作时间不如“你经常这样做”的重要性。 如果你根据用户点击GUI来启动一个线程,那么与首先单击按钮时已包含的所有延迟相比,成本很小,并且你可以使用LongRunning来避免线程 -池。 如果你正在运行一个产生大量阻塞任务的循环……那就停止这样做了。 这是一个坏主意:D

例如,假设没有异步API替代File.Exists 。 因此,如果您发现这给您带来了麻烦(例如,通过错误的网络连接),您可以使用Task.Run启动它 – 并且由于您没有进行CPU工作,因此您将使用LongRunning

相比之下,如果你需要做一些基本上100%CPU工作的图像处理,那么操作需要多长时间并不重要 – 它不是LongRunning东西。

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

现在,我们如何将此应用于您的示例? 好吧,我们不能,没有更多的信息。 如果你的代码是CPU绑定的,那么Parallel.For和friends就是你想要的 – 那些确保你只使用足够的线程来安抚CPU,并且可以使用线程池。 如果它不受 CPU限制…如果你想并行运行任务,除了使用LongRunning之外你没有任何选择。 理想情况下,这样的工作将由您可以安全地调用的异步调用组成,并await Task.WhenAll(...)来自您自己的线程的await Task.WhenAll(...)

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

绝大多数情况下,您根本不需要使用LongRunning ,因为线程池将在2秒后调整为“丢失”线程为长时间运行的操作。

LongRunning的主要问题是它会强制您使用非常危险的StartNew API 。

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

是。 首次编写代码时,不应该设置LongRunning 。 如果您看到由于线程池注入率导致的延迟,那么您可以小心地添加LongRunning

您不应该在您的情况下使用TaskCreationOptions.LongRunning 。 我会使用Parallel.For 。

如果要创建大量任务,则不使用LongRunning选项,就像在您的情况下一样。 它将用于创建将要运行很长时间的几个任务。

顺便说一下,我从来没有在任何类似的场景中使用过这个选项。

正如你所指出的, TaskCreationOptions.LongRunning的目的是

允许ThreadPool继续处理工作项,即使一个任务正在运行很长一段时间

至于何时使用它:

它本身并不是特定的长度…如果您通过性能测试发现不使用它会导致其他工作的处理长时间延迟,您通常只使用LongRunning。

资源