我尝试使用System.Threading.Tasks
库将简单的顺序循环转换为并行计算循环。
代码编译,返回正确的结果,但它不会节省任何计算成本,否则需要更长的时间。
public static class LineNet
{
public static List<Ray> SolveCpu(List<Speaker> sources, List<Receiver> targets, List<Panel> surfaces)
{
ConcurrentBag<Ray> rays = new ConcurrentBag<Ray>();
for (int i = 0; i < sources.Count; i++)
{
Parallel.For(
0,
targets.Count,
j =>
{
Line path = new Line(sources[i].Position, targets[j].Position);
Ray ray = new Ray(path, i, j);
if (Utils.CheckObstacles(ray,surfaces))
{
rays.Add(ray);
}
}
);
}
}
}
Grasshopper实施只收集sources
targets
和surfaces
,调用方法Solve
并返回rays
。
我知道将调度工作量分配到线程是昂贵的,但它是如此昂贵?
或者ConcurrentBag
只是阻止并行计算?
另外,我的类是不可变的(?),但如果我使用公共List
内核中止操作并抛出异常,是否有人能说出原因?
答案 0 :(得分:2)
如果没有可靠地再现问题的好Minimal, Complete, and Verifiable code example,就无法提供明确的答案。您发布的代码甚至看起来不是真实代码的摘录,因为声明为方法的返回类型的类型与return
语句实际返回的值不同。
但是,您发布的代码当然不能很好地使用Parallel.For()
。您的Line
构造函数相当昂贵,无法证明并行化创建项目的任务。要明确的是,这是唯一可能的胜利。
最后,您仍然需要将您创建的所有Line
个实例聚合到一个列表中,因此为Parallel.For()
任务创建的所有中间列表都只是纯粹的开销。并且聚合必然是序列化的(即,一次只有一个线程可以向result
集合添加项目),并且以最糟糕的方式(每个线程只能在放弃锁定之前添加单个项目)而另一个线程有机会接受它。)
坦率地说,最好将每个本地List<T>
存储在一个集合中,然后在Parallel.For()
返回后在主线程中一次性聚合它们。并不是说这可能会使代码比直接非并行化实现更好。但至少它不太可能变得更糟。 :)
最重要的是,您似乎没有可以从并行化中受益的工作负载。如果您不这么认为,您需要以更清晰,更详细的方式解释该思想的基础。
如果我使用公共列表,内核中止操作并抛出异常,有人能说出原因吗?
您已经使用(看起来)List<T>
作为每项任务的本地数据,事实上这应该没问题,因为任务不会共享其本地数据。
但是如果你问为什么如果你试图使用List<T>
代替ConcurrentBag<T>
result
来获得异常,那么这完全是预期的。 List<T>
类不是线程安全的,但Parallel.For()
将允许它运行的每个任务与所有其他任务同时执行localFinally
委托。所以你有多个线程都试图同时修改同一个非线程安全的集合。这是灾难的秘诀。你幸运的是你得到了例外;实际的行为是未定义的,并且您只是因为导致运行时异常而损坏数据结构。