为什么继续似乎按顺序阻止和/或运行任务
本文关键字:运行 任务 继续 顺序 为什么 | 更新日期: 2023-09-27 18:32:55
我对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 有关。有趣的是,如果我设置选项 TaskContinuationOptions.LongRun on ContinueWith 它异步运行任务而不会阻塞,我也在我的生产应用程序中完成了此操作,这已经解决了这个问题。
但是,我真的很困惑,想更好地理解为什么没有选项 TaskContinuationOptions.LongRun 的 ContinueWith 语句会导致任务阻塞或看起来按顺序运行,即使我每次迭代项目调用一个新线程。我唯一能想到的是 ContinueWith 不是在执行先前任务的线程中运行,而是在可能导致阻塞的主线程上运行。
任何帮助或建议将不胜感激。
更新
另外有趣的是,如果我更换
SomeLongRunningDatabaseCall(10001)
用类似的东西
Thread.Sleep(600000)
但是,它不会通过调用数据库来阻塞,但由于它在自己的线程中运行,因此至少其他任务不应该有任何阻塞,因为任务> 50 不会调用数据库。
每个线程都应该使用自己的数据库连接对象。我知道的大多数数据库连接都不支持非锁定多线程模式。在这些数据库连接中,线程被阻塞的情况并不罕见,因为单个连接将按顺序执行调用,而不是并行执行调用。
在这种情况下,制作 Parallel.ForEach(( 可能最终会让你的应用程序感到无聊,因为数据库连接(相对(昂贵,而且打开一大堆数据库连接实际上是不可行的。最好使用不同的模式,例如将item
对象放入ConcurrentQueue
或ConcurrentStack
中,并针对此集合抛出 3-5 个线程(或更多 - 您必须在此处进行实验(并让这些线程运行直到堆栈或队列耗尽。这样,您可以将实例化数据库连接的数量保持在安全数字,并且仍然可以避免这种令人讨厌的阻塞。
所有这些使我认为使用Parallel.ForEach
进行实际的数据库执行(这里不谈论 LINQ-to-SQL 或 EF,而是实际的数据库连接调用,如 SqlCommand.Execute
(可能是次优的。
编辑:您不必为我的建议使用旧语法。一堆任务也可以做得很好。