我对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)
但是,它并没有阻止它对数据库的调用,但由于它在自己的线程中运行,因此至少应该阻止其他任务,因为任务&gt; 50不要打电话给数据库。
答案 0 :(得分:0)
每个线程都应该使用它自己的数据库连接对象。我所知道的大多数数据库连接都不支持非锁定多线程模式。在这些数据库连接中,线程被阻塞并不罕见,因为单个连接将按顺序而不是并行执行调用。
在这种情况下,制作一个Parallel.ForEach()可能会最终导致你的应用程序失败,因为数据库连接(相对)昂贵,并且真的,打开一大堆是不可行的数据库连接。使用不同的模式可能会更好,例如将item
个对象放入ConcurrentQueue
或ConcurrentStack
并投掷3-5个线程(或更多 - 您将拥有在这里进行实验)对这个集合进行测试并让它们运行直到堆栈或队列耗尽。这样,您可以将实例化数据库连接的数量保持为安全数字,并且仍然可以避免这种令人讨厌的阻塞。
所有这些让我觉得使用Parallel.ForEach
来实际执行数据库(这里不讨论LINQ-to-SQL或EF,但实际的数据库连接调用,如SqlCommand.Execute
)可能是sub - 最优。
编辑:您不必使用旧语法来表达我的建议。一堆任务就可以做得很好。