Cal*_*ans 5 c# asynchronous task
我一直在玩并行,我在理解程序中发生的事情时遇到了一些麻烦.
我正在尝试复制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);
}
Run Code Online (Sandbox Code Playgroud)
这引发了一个奇怪的错误:
mscorlib.dll中发生了未处理的"System.AggregateException"类型异常
内部异常谈论索引超出范围.
如果我改变
Task[] tasks = new Task[engineComponents.Count];
Run Code Online (Sandbox Code Playgroud)
至
Task[] tasks = new Task[engineComponents.Count - 1];
Run Code Online (Sandbox Code Playgroud)
然后这似乎工作(或至少程序执行没有例外),但数组中没有足够的空间用于所有组件.尽管如此,所有组件都会更新,尽管tasks阵列中没有足够的空间来容纳它们.
但是,gameTime当游戏运行时,作为参数传递的对象有点疯狂.我发现很难确定这个问题,但我有两个组件,它们都只是使用一个圆圈的x位移动
x += (float)(gameTime.ElapsedGameTime.TotalSeconds * 10);
Run Code Online (Sandbox Code Playgroud)
然而,当使用时Tasks,它们的x位置很快就会彼此完全不同,而它们实际上应该是相同的.每个engineComponent.Update(gameTime)更新周期调用一次,并gameTime传递相同的对象.
当使用tasks[i].RunSynchronously();代替时tasks[i].Start();,程序完全按预期运行.
我知道以这种方式使用Tasks可能不是一个特别有效的编程实践,所以我的问题是好奇心:为什么上述代码不能像我期望的那样工作?我知道我遗漏了一些明显的东西,但我一直无法追查这个实现的具体问题.
为长期问题道歉,并感谢阅读;)
请尝试以下方法:
for (int i = 0; i < tasks.Length; i++)
{
var innerI = i;
tasks[i] = new Task(() => engineComponents[innerI].Update(gameTime));
tasks[i].Start();
}
Run Code Online (Sandbox Code Playgroud)
您需要为每个任务创建一个新变量,该变量将由linq表达式捕获并保存作业部分的索引.现在,您的所有任务都使用i变量并对最新元素执行操作.
问题是,你的lambda表达式捕获i-没有价值的i,但变量本身.
这意味着,当您的任务执行时,循环可能会在下一次迭代(甚至更晚).因此,您的某些组件可能会被多次更新,有些组件可能根本不会更新,并且最终任务很可能在i超出范围时执行engineComponents,因此也是例外.有关更多详细信息,请参阅Eric Lippert的博客文章:
解决此问题的三个选项:
就拿变量的副本内循环.在循环内声明的每个变量将被单独捕获:
for (int i = 0; i < tasks.Length; i++)
{
int copyOfI = i;
tasks[i] = new Task(() => engineComponents[copyOfI].Update(gameTime));
tasks[i].Start();
}
Run Code Online (Sandbox Code Playgroud)engineComponents[i]改为使用单独的变量捕获:
for (int i = 0; i < tasks.Length; i++)
{
var component = engineComponents[i];
tasks[i] = new Task(() => component.Update(gameTime));
tasks[i].Start();
}
Run Code Online (Sandbox Code Playgroud)如果您正在使用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());
Run Code Online (Sandbox Code Playgroud)请注意,最后一个解决方案不适用于C#4编译器,因为foreach迭代变量的行为是它是一个单一变量,就像i.您不需要以.NET 4.5或更高版本为目标,但它确实需要使用C#5编译器.
另一种选择是不使用任务明确在所有-使用Parallel.ForEach来代替:
// This replaces your entire method body
Parallel.ForEach(engineComponents, component => component.Update(gameTime));
Run Code Online (Sandbox Code Playgroud)
更简单!
| 归档时间: |
|
| 查看次数: |
571 次 |
| 最近记录: |