【问题标题】:Why c# doesn't preserve the context for an anonymous delegate calls?为什么 c# 不保留匿名委托调用的上下文?
【发布时间】:2012-01-13 16:30:39
【问题描述】:

我有以下方法:

static Random rr = new Random();
static void DoAction(Action a)
{
    ThreadPool.QueueUserWorkItem(par =>
    {
        Thread.Sleep(rr.Next(200));
        a.Invoke();
    });
}

现在我在这样的 for 循环中调用它:

for (int i = 0; i < 10; i++)
{
    var x = i;

    DoAction(() =>
    {
        Console.WriteLine(i); // scenario 1
        //Console.WriteLine(x); // scenario 2
    });
}

在场景 1 中,输出为:10 10 10 10 ... 10
在场景 2 中,输出为:2 6 5 8 4 ... 0(0 到 9 的随机排列)

你怎么解释这个? c# 是否不应该为匿名委托调用保留变量(此处为 i)?

【问题讨论】:

标签: c# .net multithreading c#-4.0 delegates


【解决方案1】:

这里的问题是有一个i 变量和十个x 的实例/副本。每个 lambda 都会引用单个变量 ix 的实例之一。每个x 只被写入一次,因此每个 lambda 都会看到一个写入它引用的值的值。

变量i 被写入,直到它达到10。没有一个lambdas 运行直到循环完成,所以他们都看到i 的最终值是10

我觉得这个例子改写如下会更清楚一些

int i = 0;  // Single i for every iteration of the loop
while (i < 10) { 
  int x = i;  // New x for every iteration of the loop 
  DoAction(() => {
    Console.WriteLine(i);
    Console.WriteLine(x);
  });
  i++;
};

【讨论】:

  • 更明确地说,每个x 不仅被写入一次,而且是不同的x,因为它每次都被初始化。在循环的生命周期中有 10 个x,只有一个i
【解决方案2】:

DoAction 产生线程,并立即返回。当线程从随机睡眠中唤醒时,循环将结束,i 的值将一直上升到 10。另一方面,x 的值被捕获并冻结 在调用之前,因此您将以随机顺序获取从09 的所有值,具体取决于基于您的随机数生成器的每个线程休眠的时间。

【讨论】:

    【解决方案3】:

    我认为使用 java 或任何面向对象的语言都会得到相同的结果(不确定,但这里似乎合乎逻辑)。

    i 的作用域是整个循环,x 的作用域是每次出现。

    Resharper 帮助您解决此类问题。

    【讨论】:

      猜你喜欢
      • 2011-02-10
      • 2010-11-01
      • 2011-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-23
      • 2012-03-08
      • 2010-12-31
      相关资源
      最近更新 更多