C # TaskFactory не завершает все итерации - PullRequest
0 голосов
/ 08 мая 2018

У меня возникают некоторые странности при использовании 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();

Это дает вывод, похожий на:

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

Я не понимаю, почему не все индексы отличны от нуля.

Спасибо

Джо

1 Ответ

0 голосов
/ 08 мая 2018

Когда вы захватываете переменную с помощью лямбды, эта переменная помещается в объект, сгенерированный компилятором, который является общим для внутренней и внешней области видимости. Когда вы делаете это:

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

Переменная 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));
}

Теперь для каждой задачи есть отдельная переменная 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));
}

В то время как новый код, преобразованный компилятором, становится

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));
}

Относительно того, почему вам пришлось использовать length - 1: Некоторые из ваших задач не выполнялись, пока цикл не был завершен. В этот момент i == length, поэтому вы получите IndexOutOfRangeException при попытке использовать i в качестве индекса в вашем массиве. Когда вы исправляете состояние гонки с помощью i, это больше не может происходить.

...