【问题标题】:Thread safety with method parameters reference vs value [duplicate]方法参数参考与值的线程安全[重复]
【发布时间】:2019-08-05 13:53:48
【问题描述】:

我对线程安全方法参数的理解是:按值传递给方法的参数作为数据的副本传递,这些数据在方法调用的参数中给出,因此它们是唯一的方法调用,并且不能被任何其他任务更改。相反,引用参数很容易被其他任务中运行的代码更改。

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

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

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

我希望得到相同的结果,因为我传递的是类型值参数。

【问题讨论】:

  • 这被称为变量捕获 - 它发生在 lambda 表达式的上下文中。
  • 问题是关于我使用 taskN 与使用 i 得到的输出之间的差异。正如@500-InternalServerError 所说,它可能是变量捕获我正在寻找的主题。我正在阅读它。
  • I get the correct output (task 1 to 10) 你的意思是 0 到 9 吗?我问的原因是这应该是你的第一个线索。一个显示 0 到 9,另一个主要显示 10。这通常表明您在 循环有效退出后使用循环变量。

标签: c# multithreading thread-safety parameter-passing


【解决方案1】:

按值传递给方法的参数作为方法调用的参数中给出的数据的副本传递,

问题真的是:复制什么时候发生?

不是当你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);
}

只有一个i,所以如果在循环之后调用了所有匿名方法,那么它们的值将是:10。 p>

将代码更改为:

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

给你非常不同的语义:

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

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

【讨论】:

  • 我想我以前从未意识到捕获 class 和捕获 instance 之间的区别,谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-26
  • 1970-01-01
  • 1970-01-01
  • 2013-09-04
相关资源
最近更新 更多