2011-11-09 12 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 

しかし、私は期待を何かが最も驚くべきものがあることである

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

のようにすべての4つの物質が含まれています私が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

あなたはループ変数の上に閉じたときに何が起こるかです::

私はBackgroundWorkerオブジェクトが好き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ループ内の変数をクローズしています。何が起こっているのは、スレッドが開始する前に変数が上書きされているため、同じ値を持つ2つの繰り返しがあるということです。

​​
+0

ありがとうございます! –

+0

JetbrainsのResharperがあなたにそのような問題を通知したことに注意してください。 (私は形や形でジェットブレーンと提携していない...ただResharperが大好き!) –

0

これはスレッド内で同じrefrenceを持つ可能性があるvarエージェントのためです。 あなたのイヤラーのアプローチはお勧めできません。

+1

thats y ....私はあなたがthatsなぜ... smsのスラングの問題を意味すると思う – Sandy

1
は、このようなあなたのforeachループの変更

簡単な修正は、それを使用する前に、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

一時とエージェントは同じrefrenceを持っています。同じ間違った出力の可能性があります。カスタムのものではなく、原始的なdatatpyeで動作します。 – ratneshsinghparihar

+2

@ user998660 - tempはforeach内でスコープされ、ループの次の反復でエージェントが変更された後でさえ、元の参照のみを保持します。 –

+0

@ジャスティン:はい、正しい。これは、参照自体がクロージャで変更されないようにするために必要なローカル参照です。 –

-1

(ManualResetEventのような)スレッド同期のいくつかの種類を使用しなければ、スレッドが処理される順序を保証することはできません。複数のステップを順番に実行したい場合は、ワークバンドルをバンドルして、すべてを単一のバックグラウンドスレッドで実行することをお勧めします。

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は、個々のスレッドで呼び出される各印刷が必要であることを明示していませんでした。多くの場合、開発者は単一のスレッドが行うときに個々のスレッドを作成します。また、新しいスレッドの内部でループが発生しているため、RunWorkingAsyncの呼び出し後にagentListを変更しないと、元のコードサンプルと同じように変数クロージャが問題になることはありません。 –