2017-01-12 32 views
5

我有以下 -使用延遲任務的內部匿名方法在一個循環內

for (int i = 0; i < N; ++i) 
{ 
    var firstTask = DoSomething(...); 

    var task = anotherTask.ContinueWith(
     (t) => ProcessResult(i, t.Result)); 
} 

問題是,我的價值傳遞到ProcessResult似乎是值在啓動時,而不是價值創建時的迭代。

防止這種情況的最佳方法是什麼?

+0

爲什麼使用'ContinueWith'而不是等待?至於'i',你的lambda捕獲*變量*而不是變量的值。當實際讀取它時,讀取'i'將返回包含'i''的任何內容 - 實際執行對'ProcessResult(i,..)'的調用時。這是預期的行爲。使用'await'將解決這個問題,通過消除lambda *和*簡化代碼 –

+0

來鏈接任務。這些可以長時間運行,而等待會暫停當前線程。 – Hector

+0

不,它不會。 'await' *等待*,不會阻止。它相當於'ContinueWith',而不是'Wait'。它使鏈接更容易*,因爲它不需要lambdas和捕獲。 –

回答

2

您需要將i的值捕獲到它自己的變量中。

for (int i = 0; i < N; ++i) 
{ 
    var count = i; 

    var firstTask = DoSomething(...); 

    var task = anotherTask.ContinueWith(
     (t) => ProcessResult(count, t.Result)); 
} 

實施例:

for (int i = 0; i < 5; ++i) 
{ 
    var a = i; 
    var task = Task.Delay(0).ContinueWith((t) => a.Dump()); 
} 

此輸出是這樣的:

0 
2 
1 
4 
3 

但這:

for (int i = 0; i < 5; ++i) 
{ 
    var task = Task.Delay(0).ContinueWith((t) => i.Dump()); 
} 

輸出:

5 
5 
5 
5 
5 
2

您需要在循環內創建一個臨時變量;在您當前的代碼中,您正在捕獲變量i,而不是值,這意味着當最後執行延續任務時,循環已完成並且iN-1

for (int i = ...) 
{ 
    var temp = i; 
    var task = anotherTask.ContinueWith(t => ProcessResult(temp, t.Resume)); 
} 
+1

或使用'await'而不是'ContinueWith' –

+1

@PanagiotisKanavos是的,但問題是關於爲什麼代碼的行爲方式,而不是如果這是完成這項工作的最佳方式。 – InBetween

+0

謝謝!我認爲編譯器/垃圾收集器可以處理循環繼續,變量不會被丟棄? – Hector

2

使用外部變量的lambda實際上捕獲變量,而不是存儲在其中的值。這意味着隨着循環的進行,您從捕獲的變量中讀取的值也會改變。

你可以通過在循環中使用一個臨時變量來解決這個問題。您的代碼將是更清潔了很多但如果你使用async/await代替ContinueWith和lambda表達式,如:

for (int i=0;i<N;i++) 
{ 
    //... 
    var result=await thatOtherAsyncMethod(...); 
    ProcessResult(i, result)); 
} 

在一般情況下,你可以通過複製循環變量進入循環的範圍內定義的變量避免捕獲的問題。

這解決了問題,因爲臨時變量只存在於循環體內。拉姆達是循環體內創建,捕捉局部的,不變的變量:

for (int i=0;i<N;i++) 
{ 
    var temp=i; 
    var myLambda = new Action(()=>MyMethod(temp)); 

    //This runs with the local copy, not i 
    myLambda(); 
} 

的雖然更好的方法,是避免捕獲和傳遞迴路值作爲狀態參數ContinueWith,例如:

for (int i = 0; i < N; ++i) 
{ 
    //... 
    var task = anotherTask.ContinueWith(
           (t,state) => ProcessResult((int)state, t.Result), 
           i); 
    //... 
}