在下面的简化代码中,我产生了200个任务。每项任务都需要经过一个由锁保护的关键区域。内部锁是一个.AsParallel()语句。当我运行程序时,没有任何反应。该程序无限期挂起,没有任何打印。为什么.AsParallel()在任务内运行时挂起?
private static object lockObject = new object();
static void Main(string[] args)
{
RunTasks();
}
private static void RunTasks()
{
List<Task> tasks = new List<Task>();
for (int i = 0; i < 200; i++)
{
tasks.Add(Task.Factory.StartNew(PerformComputations));
}
Task.WaitAll(tasks.ToArray());
}
private static void PerformComputations()
{
// Computations
lock (lockObject)
{
// The actual operations performed here are irrelevant. The key is that they use .AsParallel()
foreach (int i in Enumerable.Range(0, 500).AsParallel().Select(i => i))
{
Console.WriteLine(i);
}
}
// Additional computations
}
然而,一切都运行正常(虽然缓慢)如果RunTasks是像这样实现的:
Parallel.For(0, 200, i =>
{
PerformComputations();
});
一切都同样适用,如果我从PerformComputations删除.AsParallel()语句。
问题:
- 为什么原来的代码锁住?
- 我最好的猜测是RunTasks产生200个任务,这比我的机器上的物理内核数量多。 PerformComputations中的锁定语句确保除一个任务外的所有其他任务都被阻止。当未被阻止的线程运行并行查询时,它将排队另一个任务。但是,活动任务的最大数量已经处于活动状态,因此新任务永远处于队列中空闲状态。
- 这是准确的吗?任何人都可以指向我的文档来证实这一点或更详细地解释吗?
- 为什么修改后的RunTasks工作?
- 是不是只有Parallel.For队列少于最大活动任务数?
- 有没有办法以这样的方式编写PerformComputations,它将使用原始的RunTasks方法,但仍然并行运行?
部分猜想,500转储到控制台中看到排队真的很快,所以,做一个for循环,有效地在几毫秒即整个performcomputations做,它只是需要更长的时间控制台滚动..而因为你在修改过的东西中开始执行任务似乎更加随机,因为有些任务实际上是同时开始的,而不是一个接一个地开始,当他们有可能在下一次开始之前有时间完成时才完成? – BugFinder
在原始代码中,没有任何线程获取到Console.WriteLine语句。如果我在该行上放置一个断点,它永远不会被触发,应用程序只会继续无限期地运行而不会占用CPU。修改后的代码并非如此。 – Kvothe
你应该完全避免在任务中阻塞代码,而不是使用Task.WaitAll你应该使用Task。WhenAll'或新的async/await语法来指定完成所有这些子任务时应该发生的情况。 –