C#TaskFactory没有完成所有迭代

Joe*_*oeH 2 c# task taskfactory

使用TaskFactory时遇到一些奇怪的事情:

Task<int[]> parent = Task.Run(() =>
{
    int length = 100;
    var results = new int[length];
    TaskFactory tf = new TaskFactory(TaskCreationOptions.AttachedToParent,
        TaskContinuationOptions.ExecuteSynchronously);

    // Create a list of tasks that we can wait for
    List<Task> taskList = new List<Task>();
    for (int i = 0; i < length - 1; i++) // have to set -1 on length else out of bounds exception, huh?
    {
        taskList.Add(tf.StartNew(() => results[i] = i));
    }
    // Now wait for all tasks to complete
    Task.WaitAll(taskList.ToArray());

    return results;
});

parent.Wait();

var finalTask = parent.ContinueWith(
     parentTask =>
     {
        foreach (int i in parentTask.Result)
        {
            Console.WriteLine(i);
        }
    });

finalTask.Wait();

Console.ReadKey();
Run Code Online (Sandbox Code Playgroud)

这给出了类似于的输出:

0 0 0 0 4 5 0 0 0 0 10 0 12 13 14 ... 0 99

我不明白为什么不是所有指数都不为零.

谢谢,

Dar*_*con 8

使用lambda捕获变量时,此变量将放入编译器生成的对象中,该对象在内部和外部作用域之间共享.当你这样做:

for (int i = 0; i < length - 1; i++)
{
    taskList.Add(tf.StartNew(() => results[i] = i));
}
Run Code Online (Sandbox Code Playgroud)

变量i在循环和所有子任务之间共享.只有一个i,当任务正在运行时,循环会对其进行修改.这是一种竞争条件,每次都会在阵列中产生看似随机的数据,具体取决于任务的安排方式.

解决此问题的最简单方法是将一个不可变变量作为循环体的范围:

for (int i = 0; i < length; i++) // You can now use length instead of length - 1
{
    int innerI = i;
    taskList.Add(tf.StartNew(() => results[innerI] = innerI));
}
Run Code Online (Sandbox Code Playgroud)

现在innerI每个任务都有一个单独的变量,它只被赋值i一次,不会改变.


想象一下由编译器转换成的旧代码

class Generated1
{
    public int i;
}
var context = new Generated1(); // Exactly one copy of i
for (context.i = 0; context.i < length - 1; context.i++)
{
    taskList.Add(tf.StartNew(() => results[context.i] = context.i));
}
Run Code Online (Sandbox Code Playgroud)

而由编译器转换的新代码变为

class Generated2
{
    public int innerI;
}
for (int i = 0; i < length - 1; i++)
{
    var context = new Generated2(); // New copy of innerI for every loop iteration
    context.innerI = i;
    taskList.Add(tf.StartNew(() => results[context.innerI] = context.innerI));
}
Run Code Online (Sandbox Code Playgroud)

关于你必须使用的原因length - 1:在循环完成之前,你的一些任务没有运行.那时i == length,所以IndexOutOfRangeException当你试图i用作数组的索引时,你会得到一个.当你修复竞争条件时i,这不再发生.