【发布时间】:2016-04-08 06:17:53
【问题描述】:
在下面的简化代码中,我生成了 200 个任务。每个任务都需要经过一个由锁保护的关键区域。锁内部是一个 .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 中的 lock 语句确保除了一个任务之外的所有任务都被阻塞。当未阻塞线程运行并行查询时,它会将另一个任务排队。但是,最大数量的活动任务已经处于活动状态,因此新任务将永远闲置在队列中。
- 这是准确的吗?谁能指出我可以确认或更详细解释的文档?
- 为什么修改版的 RunTasks 可以工作?
- 难道只是 Parallel.For 队列小于最大活动任务数吗?
- 有没有办法编写 PerformComputations 以使其与原始 RunTasks 方法一起工作但仍并行运行?
【问题讨论】:
-
我的一部分人会猜测到控制台的 500 个转储会很快排队,因此,通过执行 for 循环,有效地在几毫秒内完成整个执行计算,控制台只需要更长的时间滚动..而因为您在修改后的内容中并行启动任务,它可能看起来更随机,因为有些任务实际上是同时开始的,而不是一个接一个,当它们可能有时间在下一个完全开始之前完成时?
-
在原始代码中,没有线程可以访问 Console.WriteLine 语句。如果我在该行上放置一个断点,它永远不会被击中,应用程序只是继续无限期地运行而没有 CPU 使用率。修改后的代码并非如此。
-
你应该避免在任务中完全阻塞代码,而不是使用
Task.WaitAll,你应该使用Task.WhenAll或新的async/await语法来指定当所有这些子任务完成时应该发生什么。 -
此测试没有显示 Task.StartNew 和 Parallel.For 之间有任何区别。您会发现没有任务的简单循环运行速度与第一个版本一样快。由于各种原因,这两个版本都不好,例如
lock意味着一次只能运行一个任务,使并行化毫无意义。 -
鉴于您没有此时有任何方法并行运行,您只需清理代码即可获得并行化。如果您取消锁定,则每种方法都有效。例如。 仅使用 AsParallel。仅使用 Parallel.For*。一次启动所有任务,虽然 this 在实际场景中会因为抖动而变慢
标签: c# .net multithreading task-parallel-library task