问题
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 Task
s 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