Библиотека параллельных задач и циклы - PullRequest
2 голосов
/ 12 августа 2011

Я столкнулся с ситуацией, когда задачи, которые я создаю, кажутся работающими только при отладке кода.

Как вы увидите ниже, я продолжаю получать исключение индекса вне диапазона, которое не должно быть возможным, поскольку цикл никогда не должен достигать 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();
    }
}

}

Спасибо

Mike

Ответы [ 3 ]

3 голосов
/ 12 августа 2011

ОБНОВЛЕНИЕ : В C # 5 это больше не проблема. разрыв изменение было выполнено так, что переменная цикла foreach логически находится внутри цикла.

Это еще один пример одногоиз наиболее распространенных ошибок, которые вы можете сделать в коде C #: захват переменной цикла в анонимном делегате.У Эрика Липперта есть хорошее объяснение того, что именно идет не так.

На самом деле, ваша ситуация еще хуже, потому что в теории это может произойти неправильно или случайно.Предположим, вы внесли следующее изменение:

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

Ответ Тома устраняет эту проблему, вводя новую переменную ii внутри цикла.Когда создается каждая задача, она захватывает эту переменную, которая имеет фиксированное значение для каждой итерации.

3 голосов
/ 12 августа 2011

Поскольку задача происходит вне цикла, который завершается к моменту ее выполнения, в это время i равно 5 (условие выхода из цикла).

Во втором примере вы не используете i, а вместо этого жестко закодируете значения, чтобы проблема исчезла.

В отладчике время отличается, и задачи могут выполняться до завершения цикла: снова проблема исчезает.

Я думаю, вы могли бы исправить это так:

var ii=i;
tasks[i] = Task.Factory.StartNew(() =>
                {
                    nums.Add(numbers[ii]);
                }, 
                TaskCreationOptions.None);
2 голосов
/ 12 августа 2011

Проблема здесь в том, что Задача обращается к переменной 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);
...