剛開始編程的時候,對多線程有着盲目的崇拜。遇到需要調用寫的方法,就想用多線程來進行調用。結果這幾天才發現了軟件中的BUG,看來多線程也不是想用就能用的,用不好就會非常糟糕,導致一些莫名其名的BUG。
我寫了一個數據庫的小例子,也驗證了這個BUG是確實存在的。首先呢,我在數據庫中創建了兩個字段的表格,兩個字段分別為M,N。其中M我設置為主鍵,並手動添加了從1到10的數據,再通過數據庫更新的方式來對這10個數據進行更新。
int[] array = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] array1 = new int[10] {21,22,23,24,25,26,27,28,29,30 };
private void update(int i)
{
string sqlstr = null;
sqlstr += "update Table_1 set N='"+array1[i]+ "' where M='"+ array[i] + "'";
if (db.ExecDataBySql(sqlstr)>0)
{
MessageBox.Show("zhixingchengong!");
}
}
然后添加了一個Button控件來執行調用這個方法,如下:
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 10; i++)
{
if (i > 9) i = 9;
new Task(()=> update(i)).Start() ;
}
}
之所以添加if (i > 9) i = 9;是因為程序莫名其妙的發生超出索引的異常,很是奇怪。雖然加了這個,程序依然會出現報錯,無解。執行這個程序之后,發現數據只成功更新了3個數據。
最后才看到一段類似於我這樣的多線程問題。
Lambda 表達式與被捕獲變量
如我們所見,lambda 表達式是向線程傳遞數據的最強大的方法。然而必須小心,不要在啟動線程之后誤修改被捕獲變量(captured variables)。例如,考慮下面的例子:
for (int i = 0; i < 10; i++)
new Thread (() => Console.Write (i)).Start();
輸出結果是不確定的!可能是這樣0223557799。
問題在於變量i在整個循環中指向相同的內存地址。所以,每一個線程在調用Console.Write時,都在使用這個值在運行時會被改變的變量!
類似的問題在C# 4.0 in a Nutshell的第 8 章的 “Captured Variables” 有描述。這個問題與多線程沒什么關系,而是和 C# 的捕獲變量的規則有關(在for和foreach的場景下有時不是很理想)。
解決方法就是使用臨時變量,如下所示:
for (int i = 0; i < 10; i++)
{
int temp = i;
new Thread (() => Console.Write (temp)).Start();
}
變量temp對於每一個循環迭代是局部的。所以,每一個線程會捕獲一個不同的內存地址,從而不會產生問題。我們可以使用更為簡單的代碼來演示前面的問題:
string text = "t1";
Thread t1 = new Thread ( () => Console.WriteLine (text) );
text = "t2";
Thread t2 = new Thread ( () => Console.WriteLine (text) );
t1.Start();
t2.Start();
因為兩個lambda表達式捕獲了相同的text變量,t2會被打印兩次:
t2
t2
看來線程也不是隨便用的,我還是要慢慢搞懂每一個問題,讓自己變得越來越強,程序越來越好看。