2012-10-19 128 views
4

在下面的問題中,我發現了一種以類型安全的方式調用QueueUserWorkItem的巧妙方法,在該方法中傳遞委託而不是WaitCallBack和對象。然而,它不會按照人們所期望的方式工作。帶委託的QueueUserWorkItem不起作用,但WaitCallBack不起作用

What's the difference between QueueUserWorkItem() and BeginInvoke(), for performing an asynchronous activity with no return types needed

下面是一些示例代碼和輸出演示問題。

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. 爲什麼用做它的關閉/委託方式時,是不是我的正確值傳遞?
  2. 我到底是怎樣變成10的?在循環中,它只有0-9的值是正確的?
+0

循環變量捕獲,再次。該術語的搜索堆棧溢出。 – usr

+0

@usr或者,您可以找到其中一個重複項並投票結束。這將是更有效的事情。 – casperOne

回答

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。這意味着,你排隊上十個線程非常很快,由他們開始的時候,關閉已捕獲i爲10。

要解決這個問題,可以減少封閉的範圍內,通過引入可變循環,像這樣裏面:

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

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

這裏,關閉不超出循環,只是裏面的值。

也就是說,第二次調用QueueUserWorkItem會產生所需的結果,因爲您在創建Tuple<T1, T2>時代表正在排隊,該值固定在該點。

請注意,in C# 5.0, the behavior for foreach was changed because it happens so often (where the closure closes over the loop) and causes a number of people a lot of headaches(但不是for像你正在使用)。

如果你想利用這一事實的優點,你可以叫上Enumerable classRange method使用foreach

foreach (int i in Enumerable.Range(0, 10)) 
{ 
    // Closure for anonymous function call begins here. 
    ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", i); }); 
} 
2

這是因爲如何變量捕獲:委託將在i值實際執行的時間,而不是在申報時,所以到時候他們都是10.嘗試複製到局部變量:

for (int i = 0; i < 10; ++i) 
{ 
    int j = i;   
    ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", j); }); 
+0

我明白了。謝謝!如果我不知道這一點就看到了代碼,我會認爲「wtf - 爲什麼要複製?」 – Jesse

相關問題