如何避免捕获变量?

7
我遇到了一个问题。
foreach(var category in categories)
{
    foreach(var word in words)
    {
        var waitCallback = new WaitCallback(state =>
        {
            DoSomething(word, category);
        });

        ThreadPool.QueueUserWorkItem(waitCallback);
    }
}

当执行DoSomething时,它接收到每个捕获变量的最新值,而不是我想要的值。我可以想象出一种解决方案,但我相信你们可以提出更好的解决方案。

可能是How does a lambda in C# bind to the enumerator in a foreach?的重复问题。 - JSBձոգչ
1
我不同意这是一个重复的问题。那篇帖子问的是为什么会出现这种行为;而这篇帖子问的是避免这个问题的最佳编写代码方式。 - mqp
4个回答

14

解决这个问题的标准方法是将值复制到在循环内部声明的临时变量中。

foreach(var category in categories)
{
    var catCopy = category;
    foreach(var word in words)
    {
        var wordCopy = word;
        var waitCallback = new WaitCallback(state =>
        {
            DoSomething(wordCopy, catCopy);
        });

        ThreadPool.QueueUserWorkItem(waitCallback);
    }
}

非常感谢。我已经在这个问题上纠结了大约一个小时。所以每个循环都实例化一个新的本地变量,每个变量都独立于其他变量 - 我明白了。 - redcalx

4

Refactor it to:

foreach(var category in categories) {
  foreach(var word in words) {
    DoSomethingAsync(word, category);
  }
}

...

private void DoSomethingAsync(string word, string category) {
  var waitCallback = new WaitCallback(state => DoSomething(word, category));
  ThreadPool.QueueUserWorkItem(waitCallback);
}

这很简单易懂。它陈述了开发者的意图,而不会在代码中添加额外的变量(就像默认解决此问题的方式那样)。


可能是一种品味问题,但基本上是相同的原则。在函数的情况下,通过函数参数隐式地进行复制。 - Heinzi

1

仅供参考,我认为以下内容可以解决我的问题:

foreach(var category in categories)
{
    foreach(var word in words)
    {
        var waitCallback = new WaitCallback(state =>
        {
            var kv = (KeyValuePair<string, string>)state;
            DoSomething(kv.Key, kv.Value);
        });

        var state2 = new KeyValuePair<string, string>(word, category);
        ThreadPool.QueueUserWorkItem(waitCallback, state2);
    }
}

1
是的,您也可以使用 Tuple<string, string> 代替 KeyValuePair。 - H H
@Henk 需要 .NET 4.0。 - Jader Dias
1
我现在认为这是默认设置,除非另有说明。 - H H

1

我会像这样写整个东西,避开问题并且完全没有疑问关于正在发生的事情:

var callbacks = words.SelectMany(w => categories.Select(c =>
    new WaitCallback(state => {
        DoSomething(w, c);
    })
));

foreach (var callback in callbacks)
    ThreadPool.QueueUserWorkItem(callback);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接