Run same code multiple times in parallel with different parameter

邮差的信 提交于 2021-01-27 05:33:10

问题


This very simple example:

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

Produces:

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

Which is not as expected, the parameter i isn't passed correctly. I had thought to use Action<int> to wrap the code but couldn't see how I would. I do not want to write a dedicated method like Task CreateTask(int i) I'm interested how to do it using lambdas.

What is normal way to do this - spin up the same code a bunch of times in parallel with a different parameter value?


回答1:


You've got a captured loop variable i, try to add temp variable inside a loop and pass it to the Task

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

Further reading How to capture a variable in C# and not to shoot yourself in the foot. foreach loop has the same behavior before C# 5, but according to link above

with the release of the C# 5.0 standard this behavior was changed by declaring the iterator variable inside every loop iteration, not before it on the compilation stage, but for all other constructions similar behavior remained without any changes

So, you may use foreach without temp variable




回答2:


You need to capture the value inside the for loop otherwise all of the Tasks are still referring to the same object:

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

See here for more info.




回答3:


You could use LINQ to create a closure for each lambda you pass to the Task constructor:

var tasks = Enumerable.Range(0, numLanes - 1)
    .Select(i => new Task(() => Console.WriteLine($"Lane {i}")));



回答4:


Another approach (without introducing additional variable inside for loop) is to use constructor 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 and later foreach loop introduces a new variable on each iteration. Therefore in C# 5 and later it is possible to use foreach to create tasks where each task captures its own loop variable (also no need to introduce additional variable inside loop):

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


来源:https://stackoverflow.com/questions/61343062/run-same-code-multiple-times-in-parallel-with-different-parameter

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!