2011-11-09 84 views
5

我遇到了C#线程奇怪的问题。奇怪的线程与C#

这是我使用线程“激活”agentList中每个代理的Print()函数的示例程序。

class Program { 

    static void Main(string[] args) { 

     List<Agent> agentList = new List<Agent>(); 

     agentList.Add(new Agent("lion")); 
     agentList.Add(new Agent("cat")); 
     agentList.Add(new Agent("dog")); 
     agentList.Add(new Agent("bird")); 

     foreach (var agent in agentList) { 
      new Thread(() => agent.Print()).Start(); 
     } 

     Console.ReadLine(); 
    } 
} 

class Agent { 
    public string Name { get; set; } 

    public Agent(string name) { 
     this.Name = name; 
    } 

    public void Print() { 
     Console.WriteLine("Agent {0} is called", this.Name); 
    } 
} 

这里是结果,当我运行上面的程序:

Agent cat is called 
Agent dog is called 
Agent bird is called 
Agent bird is called 

但我期望的东西包含了所有4剂,如

Agent lion is called 
Agent cat is called 
Agent dog is called 
Agent bird is called 

最令人惊奇的是,如果我在foreach外面调用线程,它就可以工作!

class Program { 
    static void Main(string[] args) { 
     List<Agent> agentList = new List<Agent>(); 

     agentList.Add(new Agent("leecom")); 
     agentList.Add(new Agent("huanlv")); 
     agentList.Add(new Agent("peter")); 
     agentList.Add(new Agent("steve")); 

     new Thread(() => agentList[0].Print()).Start(); 
     new Thread(() => agentList[1].Print()).Start(); 
     new Thread(() => agentList[2].Print()).Start(); 
     new Thread(() => agentList[3].Print()).Start(); 


     Console.ReadLine(); 
    } 
} 

上述代码的结果正是我所期望的。那么这里有什么问题?

+9

这就是当你关闭了循环变量会发生什么:http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing -over-the-loop-variable-considered-harmful.aspx – dlev

+1

@dlev,你应该发布一个答案,以便他可以标记它已解决。你的回答是正确的。 –

+0

你知道,如果你使用的是.Net 4.0,你可以在每个循环中使用Parallel来完成你想要的任务。我认为这样会更合适。 – jlafay

回答

9

你在那里有一个封闭。你正在关闭一个foreach循环内的变量。发生的事情是变量在线程启动前被覆盖,所以你有两次迭代具有相同的值。

最简单的解决方法是使用它之前捕获的foreach循环内的值:

​​
+0

非常感谢你! –

+0

请注意,Jetbrains的Resharper会通知您此类问题。 (我不属于Jetbrains的形状或形式......只是爱Resharper!) –

0

这是因为var代理可能在线程中具有相同的参考。 这就是为什么不推荐你的直立式方法。

+1

多数民众赞成在....我认为你的意思多数民众赞成在为什么...短信俚语问题 – Sandy

1

更改您的foreach循环是这样的:

foreach (var agent in agentList) 
{ 
    var agent1 = agent; 
    new Thread(() => agent1.Print()).Start();   
} 

的值复制到本地变量(看起来有点愚蠢)避免使用可能在运行时会改变的变量的线程。

2

您正在关闭中捕获agent,这可能在多线程中存在问题。分配给一个局部变量第一:

foreach (var agent in agentList) { 
     var temp = agent; 
     new Thread(() => temp.Print()).Start(); 
    } 
+0

温度和代理将有相同的参考。所以会有相同错误输出的机会。将适用于原始datatpye而不是自定义的。 – ratneshsinghparihar

+2

@ user998660 - temp的范围在foreach中,即使在循环的下一次迭代中更改了代理后,也只会具有原始引用。 –

+0

@Justin:是的,对。这是本地引用,需要保持引用本身不受闭包变化的影响。 –

-1

不使用某种线程同步的(如ManualResetEvent的),你不能保证线程将被处理的顺序。如果您想按顺序执行多个步骤,我会建议您将工作捆绑起来,然后在单个后台线程中执行所有工作。

我喜欢BackgroundWorker对象:

List<Agent> agentList = new List<Agent>(); 

agentList.Add(new Agent("leecom")); 
agentList.Add(new Agent("huanlv")); 
agentList.Add(new Agent("peter")); 
agentList.Add(new Agent("steve")); 

BackgroundWorker worker = new BackgroundWorker(); 
worker.DoWork += (sender, e) => 
{ 
    foreach (var item in agentList) 
    { 
     item.Print(); 
    } 
}; 

worker.RunWorkerAsync(); 
+0

哦,是的,其他答案是正确的,这是一个封闭。我仍然使用我的代码,但这是我的味道。 :D –

+0

你的代码是根本不同的。它只会使用一个单一的后台工作人员,而OP则需要为每个代理单独设置一个工作人员。你仍然需要修改代码中的闭包。 –

+0

OP从未明确表示他们希望在各个线程中调用每个打印。很多时候开发人员创建单独的线程,当一个线程会做。此外,因为循环发生在新线程内部,所以变量closure与原始代码示例没有问题,只要它们在调用RunWorkingAsync后不修改agentList,我的代码示例就能正常工作。 –