我遇到的情况是,我正在创建的任务在我调试代码时似乎只能工作。
正如您将在下面看到的那样,我继续得到一个超出范围异常的索引,这是不可能的,因为循环永远不会达到5。
如果我在循环中添加一个断点,然后继续按F10直到程序结束,所有都按预期工作。
任何想法我做错了什么?
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();
}
}
}
我希望看到1,2,3,4,5,但不能以任何特定顺序运行async
奇怪的是,这确实有效,但我没有看到除了额外打字之外的其他区别。
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();
}
}
}
由于
麦克
答案 0 :(得分:3)
由于任务发生在循环之外,在任务执行时已经完成,所以此时我是5(循环退出条件)。
在你的第二个例子中,你不使用i,而是对值进行硬编码,以便问题消失。
在调试器中,时间不同,任务可以在循环完成之前执行:同样,问题消失了。
我认为你可以这样解决:
var ii=i;
tasks[i] = Task.Factory.StartNew(() =>
{
nums.Add(numbers[ii]);
},
TaskCreationOptions.None);
答案 1 :(得分:3)
更新:在C#5中,这不再是一个问题。 breaking change was使得foreach的循环变量在逻辑上位于循环内。
这是您可以在C#代码中犯下的最常见错误之一的另一个示例:在匿名委托中捕获循环变量。 Eric Lippert有一个good explanation出现了什么问题。
实际上,你的情况更糟,因为理论上它可能是随机的对错。假设您进行了以下更改:
for (int i = 0; i < numbers.Length; i++)
{
tasks[i] = ...;
tasks[i].Wait();
}
然后你的程序突然按预期工作。原因是,当任务执行时,您的任务将获取i
(循环变量)的值,而不是创建该任务时i
的值。因此,原始程序中可以使用以下顺序:
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
Tom的回答通过在循环中引入一个新的变量ii
来解决这个问题。创建每个任务时,它会捕获此变量,该变量具有每次迭代的固定值。
答案 2 :(得分:2)
这里的问题是Task访问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);