
在使用foreach對(duì)異步委托賦值的時(shí)候,發(fā)現(xiàn)一個(gè)問(wèn)題。代碼如下:

static void Main(string[] args)
{
List lst_tsk = new List();
List lst_item = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
foreach (var item in lst_item)
{
Task tsk = new Task(() =>
{
Console.WriteLine(item);
});
lst_tsk.Add(tsk);
tsk.Start();
}
Console.ReadLine();
} 往Task中,賦值一個(gè)拉姆達(dá)表達(dá)式,期待運(yùn)行的結(jié)果應(yīng)該是1,2,3,4,5,6,7,8,9,10亂序輸出。但是實(shí)際上的結(jié)果是10,10,10,10,10,10,10,10,10,10。很多人都認(rèn)為這是c#編譯器的一個(gè)bug。Eric做出了解釋,根據(jù)Eric的文章,在foreach循環(huán)語(yǔ)句中的變量只有一個(gè)item,該變量在循環(huán)過(guò)后,被賦值為10了。當(dāng)異步線程啟動(dòng)的時(shí)候,取到的item早就變成10了,因此就得出上面的結(jié)果。
根據(jù)Eric的文章,foreach只是一個(gè)語(yǔ)法糖,它對(duì)應(yīng)的代碼如下
IEnumeratore = ((IEnumerable )values).GetEnumerator(); try { int m; // OUTSIDE THE ACTUAL LOOP while(e.MoveNext()) { m = (int)(int)e.Current; funcs.Add(()=>m); } } finally { if (e != null) ((IDisposable)e).Dispose(); }
可以看到m并不包括在while語(yǔ)句中,而且()=>m的意思是返回當(dāng)前m變量的值,而不是返回委托創(chuàng)建時(shí)m變量的值。因此當(dāng)這個(gè)委托真正運(yùn)行的時(shí)候,找到的m可能已經(jīng)是其它值了。
如果把語(yǔ)法糖改成如下的方式:
try
{
while(e.MoveNext())
{
int m; // INSIDE
m = (int)(int)e.Current;
funcs.Add(()=>m);
}
}那么m在while內(nèi)部,每一個(gè)m都是單獨(dú)的。根據(jù)Eric,不這樣改的一個(gè)原因就是,它可能會(huì)增加了在循環(huán)中使用閉包的次數(shù),(因?yàn)楫惒骄€程在啟動(dòng)時(shí),都會(huì)用到循環(huán)中的m,這個(gè)m的生命周期在while循環(huán)中,只能通過(guò)閉包機(jī)制,使得其值能夠繼續(xù)保留在內(nèi)存中,能夠讓異步委托在調(diào)用的時(shí)候繼續(xù)訪問(wèn)到該值)。而且,如果這樣修改了,用戶會(huì)覺(jué)得foreach每一個(gè)循環(huán)都使用了一個(gè)新的變量,而不是一個(gè)存儲(chǔ)了新值的舊變量。
因此,一開始的演示代碼,只需要如下修改既可以了:
static void Main(string[] args)
{
List lst_tsk = new List();
List lst_item = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
foreach (var item in lst_item)
{
var copy= item;//增加一個(gè)臨時(shí)的拷貝變量
Task tsk = new Task(() =>
{
Console.WriteLine(copy );
});
lst_tsk.Add(tsk);
tsk.Start();
}
Console.ReadLine();
} 這樣的話,每次委托運(yùn)行的時(shí)候,都會(huì)去找copy 變量了。
可能是很多人的意見(jiàn)影響了C#編譯器團(tuán)隊(duì),在C#5.0中,他們決定修改這個(gè)問(wèn)題,foreach循環(huán)中的變量存在于循環(huán)中,因此每次循環(huán)都使用的是一個(gè)新的變量。for循環(huán)暫時(shí)不做修正。因此,演示代碼在VS2012下,使用C#5.0的編譯器編譯,得到的結(jié)果是如預(yù)期那樣的亂序輸出。
創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國(guó)云服務(wù)器,動(dòng)態(tài)BGP最優(yōu)骨干路由自動(dòng)選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機(jī)房獨(dú)有T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進(jìn)行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動(dòng)現(xiàn)已開啟,新人活動(dòng)云服務(wù)器買多久送多久。