C# 4.0 任务并行库和循环
我遇到了这样一种情况:我正在创建的任务似乎只有在调试代码时才起作用 正如您将在下面看到的,我不断得到一个索引超出范围的异常,这应该是不可能的,因为循环永远不会达到5 如果我在循环中添加一个断点,然后一直按F10直到程序结束,所有操作都会按预期进行 你知道我做错了什么吗C# 4.0 任务并行库和循环,c#-4.0,task-parallel-library,C# 4.0,Task Parallel Library,我遇到了这样一种情况:我正在创建的任务似乎只有在调试代码时才起作用 正如您将在下面看到的,我不断得到一个索引超出范围的异常,这应该是不可能的,因为循环永远不会达到5 如果我在循环中添加一个断点,然后一直按F10直到程序结束,所有操作都会按预期进行 你知道我做错了什么吗 using System; using System.Collections.Generic; using System.Threading.Tasks; namespace ConsoleApplication1 { clas
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int[] numbers = new int[5] { 1, 2, 3, 4, 5 };
List<int> nums = new List<int>();
var tasks = new Task[5];
for (int i = 0; i < numbers.Length; i++)
{
tasks[i] = Task.Factory.StartNew(() =>
{
nums.Add(numbers[i]);
},
TaskCreationOptions.None);
}
Task.WaitAll(tasks);
for (int i = 0; i < nums.Count; i++)
{
Console.WriteLine(nums[i]);
}
Console.ReadLine();
}
}
使用系统;
使用System.Collections.Generic;
使用System.Threading.Tasks;
命名空间控制台应用程序1
{
班级计划
{
静态void Main(字符串[]参数)
{
int[]number=新的int[5]{1,2,3,4,5};
List nums=新列表();
var任务=新任务[5];
for(int i=0;i
{
增加(数字[i]);
},
任务创建选项。无);
}
Task.WaitAll(任务);
对于(int i=0;i
}
我希望看到1、2、3、4、5,但不是以任何特定顺序运行异步
奇怪的是,这确实有效,但除了额外的打字之外,我看不出有什么区别
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int[] numbers = new int[5] { 1, 2, 3, 4, 5 };
List<int> nums = new List<int>();
var tasks = new Task[5]
{
Task.Factory.StartNew(() => {nums.Add(numbers[0]);}, TaskCreationOptions.None),
Task.Factory.StartNew(() => {nums.Add(numbers[1]);}, TaskCreationOptions.None),
Task.Factory.StartNew(() => {nums.Add(numbers[2]);}, TaskCreationOptions.None),
Task.Factory.StartNew(() => {nums.Add(numbers[3]);}, TaskCreationOptions.None),
Task.Factory.StartNew(() => {nums.Add(numbers[4]);}, TaskCreationOptions.None)
};
Task.WaitAll(tasks);
for (int i = 0; i < nums.Count; i++)
{
Console.WriteLine(nums[i]);
}
Console.ReadLine();
}
}
使用系统;
使用System.Collections.Generic;
使用System.Threading.Tasks;
命名空间控制台应用程序1
{
班级计划
{
静态void Main(字符串[]参数)
{
int[]number=新的int[5]{1,2,3,4,5};
List nums=新列表();
var任务=新任务[5]
{
Task.Factory.StartNew(()=>{nums.Add(数字[0]);},TaskCreationOptions.None),
Task.Factory.StartNew(()=>{nums.Add(number[1]);},TaskCreationOptions.None),
Task.Factory.StartNew(()=>{nums.Add(number[2]);},TaskCreationOptions.None),
Task.Factory.StartNew(()=>{nums.Add(number[3]);},TaskCreationOptions.None),
Task.Factory.StartNew(()=>{nums.Add(number[4]);},TaskCreationOptions.None)
};
Task.WaitAll(任务);
对于(int i=0;i
}
谢谢
Mike由于任务发生在循环之外,而循环在任务执行时已经完成,此时i为5(循环退出条件) 在第二个示例中,您不使用i,而是对值进行硬编码,以便问题消失 在调试器中,时间是不同的,任务可以在循环完成之前执行:同样,问题消失了 我想你可以这样修理它:
var ii=i;
tasks[i] = Task.Factory.StartNew(() =>
{
nums.Add(numbers[ii]);
},
TaskCreationOptions.None);
这里的问题是,任务访问i变量,在任务运行时,该变量将被更改,并且在上一次迭代中获得异常的原因是变量i将为5,虽然循环将在该增量后停止,但任务体仍引用它,因此此行将抛出
nums.Add(numbers[i]);
因为很明显5超出了范围值
要解决这个问题,需要将i作为状态参数传递给StartNew方法
tasks[i] = Task.Factory.StartNew((obj) =>
{
int index = (int) obj;
nums.Add(numbers[index]);
},
i);
更新:在C#5中,这不再是一个问题。使foreach的循环变量在逻辑上位于循环内部的方法 这是您在C#代码中可能犯的最常见错误之一的另一个示例:在匿名委托中捕获循环变量。埃里克·利珀特对到底出了什么问题有自己的看法 事实上,你的情况甚至更糟,因为理论上它可能会随机出现对错。假设您进行了以下更改:
for (int i = 0; i < numbers.Length; i++)
{
tasks[i] = ...;
tasks[i].Wait();
}
Tom的答案通过在循环中引入新变量
ii
解决了这个问题。创建每个任务时,它捕获这个变量,每个迭代都有一个固定的值。请澄清,您是说第一段代码不起作用,但第二段代码起作用,对吗?我想这就是Resharper在哀求您“访问修改的闭包”时所说的:作为进一步说明,我发现使用tasks=newlist()和tasks.Add(mytask)更容易;然后执行Task.WaitAll(tasks.toArray());
i = 0
Create task 0
i = 1
Run task 0: This task sees i = 1
Create task 1
i = 2
Create task 2
i = 3
Create task 3
i = 4
Create task 4
i = 5
Run task 1: This task sees i = 5 and throws an exception