我一直在玩弄并行性,但我在理解我的程序中发生的事情时遇到了一些困难。
我正在尝试复制 XNA 框架的一些功能。我正在使用组件式设置,我想使我的程序更高效的一种方法是调用 Update
每个组件的方法在一个单独的Task
.但是,我显然做错了一些可怕的事情。
我在循环中用于更新调用的代码是:
public void Update(GameTime gameTime)
{
Task[] tasks = new Task[engineComponents.Count];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = new Task(() => engineComponents[i].Update(gameTime));
tasks[i].Start();
}
Task.WaitAll(tasks);
}
这会引发一个奇怪的错误:
An unhandled exception of type 'System.AggregateException' occurred in mscorlib.dll
内部异常谈论索引超出范围。
如果我改变
Task[] tasks = new Task[engineComponents.Count];
到
Task[] tasks = new Task[engineComponents.Count - 1];
那么这似乎可行(或者至少程序无一异常(exception)地执行),但数组中没有足够的空间容纳所有组件。尽管如此,所有组件都已更新,尽管 tasks
中没有足够的空间。数组来容纳它们。
然而,gameTime
作为参数传递的对象在游戏运行时变得有些疯狂。我发现很难确定问题所在,但我有两个组件,它们都使用
x += (float)(gameTime.ElapsedGameTime.TotalSeconds * 10);
但是,当使用 Tasks
时,它们的 x 位置很快就会变得完全不同,而实际上它们应该是相同的。每个engineComponent.Update(gameTime)
每个更新周期调用一次,相同的 gameTime
传递对象。
使用 tasks[i].RunSynchronously();
时代替 tasks[i].Start();
,程序完全按预期运行。
我知道以这种方式使用任务可能不是一种特别有效的编程实践,所以我的问题是出于好奇:为什么上面的代码没有像我预期的那样工作?我知道我遗漏了一些明显的东西,但我一直无法查明此实现的具体问题。
很抱歉问了这么长的问题,感谢阅读;)
最佳答案
问题是您的 lambda 表达式正在捕获 i
- 不是 i
的 value,而是变量本身。
这意味着当您的任务执行时,循环很可能在下一次迭代(甚至更晚)。所以你的一些组件可能更新不止一次,有些可能根本就没有更新,最后的任务很可能在 i
超出 engineComponents
的范围时执行,因此异常(exception)。有关详细信息,请参阅 Eric Lippert 的博客文章:
解决此问题的三个选项:
获取循环内变量的副本。循环内声明的每个变量将被单独捕获:
for (int i = 0; i < tasks.Length; i++) { int copyOfI = i; tasks[i] = new Task(() => engineComponents[copyOfI].Update(gameTime)); tasks[i].Start(); }
改为在单独的变量中捕获
engineComponents[i]
:for (int i = 0; i < tasks.Length; i++) { var component = engineComponents[i]; tasks[i] = new Task(() => component.Update(gameTime)); tasks[i].Start(); }
如果您使用的是 C# 5,则使用
foreach
循环将执行您想要的操作:var tasks = new List<Task>(); foreach (var component in engineComponents) { Task task = new Task(() => component.Update(gameTime)); tasks.Add(task); task.Start(); } Task.WaitAll(tasks.ToArray());
请注意,最后一个解决方案不适用于 C# 4 编译器,因为 foreach
迭代变量的行为是将其作为单个变量,就像我
。您不需要以 .NET 4.5 或更高版本为目标,但您需要使用 C# 5 编译器。
另一种选择是完全显式不使用任务——使用Parallel.ForEach
相反:
// This replaces your entire method body
Parallel.ForEach(engineComponents, component => component.Update(gameTime));
简单多了!
关于C# 任务未按预期工作。奇怪的错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20853831/