C#中循环中的多线程让你感到困惑了吗?

10

可能是重复问题:
C#中的捕获变量循环

我对多线程编程还不是很熟悉。当我运行下面的代码时,只有最后一个子任务被执行了。请问发生了什么事情?非常感谢。

private void Process()
{
    Dictionary<int, int> dataDict = new Dictionary<int, int>();
    dataDict.Add(1, 2000);
    dataDict.Add(2, 1000);
    dataDict.Add(3, 4000);
    dataDict.Add(4, 3000);

    foreach (KeyValuePair<int, int> kvp in dataDict)
    {
        Console.WriteLine("Ready for [" + kvp.Key.ToString() + "]");
        Task.Factory.StartNew(() => DoSomething(kvp.Value, kvp.Key));
    }

private static void DoSomething(int waitTime, int childID)
{
    {               
        Console.WriteLine("Start task [" + childID.ToString() + "]");
        Thread.Sleep(waitTime);
        Console.WriteLine("End task [" + childID.ToString() + "]");
    }
}
输出
Ready for [1]
Ready for [2]
Ready for [3]
Ready for [4]
Start task [4]
Start task [4]
Start task [4]
Start task [4]
End task [4]
End task [4]
End task [4]
End task [4]
2个回答

12

当你在lambda中使用循环变量时,它们实际上都是引用同一个变量,即字典在运行时的最后一项。

在将其传递给lambda之前,你需要将循环变量分配给循环内的另一个变量。请执行以下操作:

foreach (KeyValuePair<int, int> kvp in dataDict)
{
    var pair = kvp;
    Console.WriteLine("Ready for [" + pair.Key.ToString() + "]");
    Task.Factory.StartNew(() => DoSomething(pair.Value, pair.Key));
}

编辑:看起来这个小问题在C#5中已经修复了,所以它可能适用于其他人;)请参见labroo的评论


2
人们应该真正解释为什么他们要点踩,对我来说这似乎是一个合理的解释。 - Nadir Muzaffar
2
为什么会有人点踩呢,我不确定这是否是解决方案,但它是一个有效的更改。请参考此链接:http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx - labroo
顺便提一句,Botz3000的答案意味着所有线程都会运行... 然而,当它们有机会运行时,kvp == 4。 - Nadir Muzaffar
我也想到了,但我就是不明白为什么。 - Dreteh
为此,您需要多了解一下lambda函数如何捕获作用域。 - Nadir Muzaffar
显示剩余4条评论

1

您可以通过在for循环中将kvp分配给一个本地变量并将变量的字段Key和Value传递给DoSomething方法来防止这种行为。


谢谢。我也想到了,但我不明白为什么。 - Dreteh

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