具有方法参数参考值与值的线程安全

s.d*_*uro 0 c# multithreading parameter-passing thread-safety

我对线程安全的方法参数的理解是:通过值传递到方法中的参数作为方法调用的参数中给出的数据的副本传递,因此它们对于该方法调用是唯一的,不能由任何其他任务更改。相反,参考参数可能会因在其他任务中运行的代码而易于更改。

话虽如此,我仍然不太清楚为什么下面的代码(不制作循环计数器的本地副本)在每个线程中返回相同的数字。

static void ExampleFunc(int i) =>
            Console.WriteLine("task " + i);
Run Code Online (Sandbox Code Playgroud)
for (int i = 0; i < 10; i++)
{
    int taskN = i; //local copy instead of i
    Task.Run(() => Func(i));
}
Run Code Online (Sandbox Code Playgroud)

实际输出是:任务10十次,
我通过传递taskN而不是i来获得正确的输出(任务1到10)。

由于传递了类型值参数,因此我期望得到相同的结果。

Mar*_*ell 5

通过值传递给方法的参数将作为方法调用的参数中给出的数据副本提供,

问题确实是:何时复制?

是不是当你Task.Run(...);; 而是- 在实际的lambda由线程池调用时,即在Func(i)执行时。这里的问题是,在大多数情况下,线程池的速度将比活动线程上的循环慢,因此所有这些都将在循环完成之后发生,并且它们都将访问相同的捕获值i。最终,您拥有的是:

class CaptureContext {
    public int i;
    public void Anonymous() { Func(i); }
}
...
var ctx = new CaptureContext();
for (ctx.i = 0; ctx.i < 10; ctx.i++)
{
    int taskN = ctx.i; // not used, so will probably be removed
    Task.Run(ctx.Anonymous);
}
Run Code Online (Sandbox Code Playgroud)

只有一个i,因此如果所有匿名方法在循环后被调用,则所有这些方法的值将为:10

将代码更改为:

int taskN = i; //local copy instead of i
Task.Run(() => Func(taskN));
Run Code Online (Sandbox Code Playgroud)

给您非常不同的语义:

class CaptureContext {
    public int taskN;
    public void Anonymous() { Func(taskN);}
}
...
for (int i = 0 ; i < 10 ; i++)
{
    var ctx = new CaptureContext();
    ctx.taskN = i;
    Task.Run(ctx.Anonymous);
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们现在有10个捕获上下文实例,每个实例都有自己的taskN值,每个实例唯一。