【发布时间】:2014-12-03 18:12:18
【问题描述】:
我对 ContinueWith 的工作方式感到困惑,它似乎阻止了 ThreadPool 并按顺序运行任务。以我编写的以下代码示例为例:
var items = new List<int>();
for (int i = 0; i < 100; i++)
{
items.Add(i);
}
Parallel.ForEach(items, item =>
{
Task.Factory.StartNew(() =>
{
}).ContinueWith(t =>
{
if (item < 50)
{
System.Console.WriteLine("Blocking in {0}", item.ToString());
var x = SomeLongRunningDatabaseCall(10001);
}
System.Console.WriteLine("Item {0}", item);
});
});
代码的目的是反映我在生产应用程序中遇到的可疑线程阻塞问题。令我惊讶的是,我发现问题似乎与我对 ContinueWith 的使用有关。有趣的是,如果我在 ContinueWith 上设置选项 TaskContinuationOptions.LongRunning 它会异步运行任务而不会阻塞,我也在我的生产应用程序中这样做了,这已经解决了这个问题。
但是,我真的很困惑,想更好地理解为什么没有选项 TaskContinuationOptions.LongRunning 的 ContinueWith 语句会导致任务阻塞或看起来按顺序运行,即使我在每次迭代项目时都调用一个新线程。我唯一能想到的是 ContinueWith 不是在执行前面任务的线程中运行,而是在可能导致阻塞的主线程上运行。
任何帮助或建议将不胜感激。
更新
另外有趣的是,如果我替换
SomeLongRunningDatabaseCall(10001)
类似
Thread.Sleep(600000)
它不会因为调用数据库而阻塞,但由于它在自己的线程中运行,因此至少不应该对其他任务造成任何阻塞,因为大于 50 的任务不会调用数据库。
【问题讨论】:
-
对
ContinueWith的调用几乎可以肯定没有阻塞。实际操作很可能是按顺序运行的;如果是这样,那是由于他们正在做的事情的性质,而不是ContinueWith的实施方式。这就是您看到Sleep调用被并行化的原因。 -
@Servy 啊,我明白你的意思了。考虑到它们应该在单独的线程上运行,您如何解释 > 50 的任务在其他任务完成之前不会运行这一事实。 (不知道你有没有机会看到我的更新)
-
这只是说明某些任务似乎正在等待其他任务。您不能拥有无限程度的并行性。对于不能超过 50 个并行数据库调用,我当然并不感到惊讶,我也不希望更多的并行运行会带来性能优势。
-
@Servy 我现在看到了,谢谢。因此,通过在 ContinueWith 上设置 TaskContinuationOptions.LongRunning,您是否会说每个线程都在单独的线程池中运行,因此看起来好像没有阻塞。
-
它会导致创建新线程,而不是使用线程池。如果线程池阻碍了你,那么这会影响它。如果阻塞来自其他地方,那么它不会。
标签: c# multithreading task-parallel-library