4

以下の質問で、WaitCallBackとオブジェクトの代わりにデリゲートを渡す、タイプセーフな方法でQueueUserWorkItemを呼び出すためのこの巧妙なトリックを見つけました。ただし、期待どおりには機能しません。

リターンタイプを必要とせずに非同期アクティビティを実行するためのQueueUserWorkItem()とBeginInvoke()の違いは何ですか

問題を示すサンプルコードと出力を次に示します。

for (int i = 0; i < 10; ++i)
{
    // doesn't work - somehow DoWork is invoked with i=10 each time!!!
    ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", i); });

    // not type safe, but it works
    ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), Tuple.Create("    WCB", i));
}

void DoWork(string s, int i)
{
    Console.WriteLine("{0} - i:{1}", s, i);
}

void DoWork(object state)
{
    var t = (Tuple<string, int>)state;
    DoWork(t.Item1, t.Item2);
}

そしてここに出力があります:

closure - i:10
    WCB - i:0
closure - i:10
    WCB - i:2
    WCB - i:3
closure - i:10
    WCB - i:4
closure - i:10
    WCB - i:5
closure - i:10
    WCB - i:6
closure - i:10
    WCB - i:7
closure - i:10
    WCB - i:8
closure - i:10
    WCB - i:9
    WCB - i:1
closure - i:10

クロージャーを使用してQueueUserWorkitemを呼び出す場合は、i = 10を呼び出しますが、WaitCallBackを使用する場合は、正しい値0〜9を取得することに注意してください。

だから私の質問は:

  1. クロージャ/デリゲートの方法を使用すると、iの正しい値が渡されないのはなぜですか?
  2. いったいどうやって10歳になるの?ループでは、0〜9の値しかありませんでしたか?
4

2 に答える 2

6

両方の質問への回答は、匿名メソッドを作成するときのクロージャの範囲に関連しています。

これを行う場合:

// Closure for anonymous function call begins here.
for (int i = 0; i < 10; ++i)
{
    // i is captured
    ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", i); });
}

ループ全体でキャプチャiしています。つまり、10 個のスレッドを非常に迅速にキューに入れ、それらが開始されるまでに、クロージャが10 にキャプチャされていることを意味します。i

これを回避するには、次のように loop 内に変数を導入して、クロージャーのスコープを縮小します。

for (int i = 0; i < 10; ++i)
{
    // Closure extends to here.
    var copy = i;

    // **copy** is captured
    ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", copy); });
}

ここでは、クロージャーはループを超えて拡張されませんが、内部の値にのみ拡張されます。

つまり、デリゲートがキューに入れられた時点で をQueueUserWorkItem作成したため、 の 2 回目の呼び出しで目的の結果が生成され、値はその時点で固定されます。Tuple<T1, T2>

C# 5.0 では、foreach非常に頻繁に発生し (ク​​ロージャーがループを閉じる場所)、多くの人々に多くの頭痛の種を引き起こすため(ただし、使用しているようではありません) の動作が変更されたことに注意してくださいfor

その事実を利用したい場合は、使用するクラスのRangeメソッドを呼び出すことができます:Enumerableforeach

foreach (int i in Enumerable.Range(0, 10))
{
    // Closure for anonymous function call begins here.
    ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", i); });
}
于 2012-10-19T15:51:59.467 に答える
2

これは、変数のキャプチャ方法によるものです。デリゲートはi、宣言時ではなく、実際の実行時に の値を取得するため、その時点ではすべて 10 になっています。ローカル変数へのコピーを試してください。

for (int i = 0; i < 10; ++i)
{
    int j = i;        
    ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", j); });
于 2012-10-19T15:52:11.410 に答える