Запускать один и тот же код несколько раз параллельно с другим параметром - PullRequest
5 голосов
/ 21 апреля 2020

Это очень простой пример:

        int numLanes = 8;
        var tasks = new List<Task>();
        for (var i = 0; i < numLanes; ++i)
        {
            var t = new Task(() =>
            {
                Console.WriteLine($"Lane {i}");
            });
            tasks.Add(t);
        }
        tasks.ForEach((t) => t.Start());
        Task.WaitAll(tasks.ToArray());

Производит:

Lane 8
Lane 8
Lane 8
Lane 8
Lane 8
Lane 8
Lane 8
Lane 8

Что не соответствует ожиданиям, параметр i не передан правильно. Я думал использовать Action<int>, чтобы обернуть код, но не мог понять, как я это сделаю. Я не хочу писать специальный метод, такой как Task CreateTask(int i). Мне интересно, как это сделать, используя лямбды.

Какой нормальный способ сделать это - раскручивать один и тот же код несколько раз параллельно с другое значение параметра?

Ответы [ 4 ]

7 голосов
/ 21 апреля 2020

У вас есть захваченная переменная l oop i, попробуйте добавить переменную temp внутри al oop и передать ее в Task

for (var i = 0; i < numLanes; ++i)
{
    var temp = i;
    var t = new Task(() =>
    {
        Console.WriteLine($"Lane {temp}");
    });
    tasks.Add(t);
}

Дальнейшее чтение Как перехватить переменную в C# и не выстрелить себе в ногу . foreach l oop имеет такое же поведение до C# 5, но согласно ссылке выше

с выпуском стандарта C# 5.0 это поведение было изменено путем объявления переменной итератора внутри каждой итерации l oop, а не перед ней на этапе компиляции, но для всех других конструкций подобное поведение осталось без изменений

Таким образом, вы можете использовать foreach без временной переменной

5 голосов
/ 21 апреля 2020

Вам нужно захватить значение внутри for l oop, в противном случае все Task s по-прежнему ссылаются на один и тот же объект:

for (var i = 0; i < numLanes; ++i)
{
    var innerI = I; // Copy the variable here
    var t = new Task(() =>
    {
        Console.WriteLine($"Lane {innerI}");
    });
    tasks.Add(t);
}

См. здесь для получения дополнительной информации.

2 голосов
/ 21 апреля 2020

Вы можете использовать LINQ для создания замыкания для каждой лямбды, которую вы передаете конструктору Task:

var tasks = Enumerable.Range(0, numLanes - 1)
    .Select(i => new Task(() => Console.WriteLine($"Lane {i}")));
0 голосов
/ 21 апреля 2020

Другой подход (без введения дополнительной переменной внутри for l oop) заключается в использовании конструктора Task(Action<object>, object):

int numLanes = 8;
var tasks = new List<Task>();

for (int i = 0; i < numLanes; ++i)
{
    // Variable "i" is passed as an argument into Task constructor.
    var t = new Task(arg =>
    {
        Console.WriteLine("Lane {0}", arg);
    }, i);
    tasks.Add(t);
}

tasks.ForEach((t) => t.Start());
Task.WaitAll(tasks.ToArray());

In C# 5 и позже foreach l oop вводит новую переменную на каждой итерации. Поэтому в C# 5 и более поздних версиях можно использовать foreach для создания задач, в которых каждая задача захватывает свою собственную переменную l oop (также нет необходимости вводить дополнительную переменную внутри l oop):

int numLanes = 8;
var tasks = new List<Task>();

foreach (int i in Enumerable.Range(0, numLanes))
{
    // A new "i" variable is introduced on each iteration.
    // Therefore each task captures its own variable.
    var t = new Task(() =>
    {
        Console.WriteLine("Lane {0}", i);
    });
    tasks.Add(t);
}

tasks.ForEach((t) => t.Start());
Task.WaitAll(tasks.ToArray());
...