上一篇中講述了簡單的C#多線程程式編寫,應該說並不具備太多的痛點,這個程式中,我們將編寫一個比較特殊的線程,兩個線程需要操作同一個List<int>對象:
代碼 1 class ThreadCollection{
2 List<int> elements = new List<int>();
3 public ThreadCollection(){
4 elements.Add(10);
5 elements.Add(20);
6 }
7
8 public void Run()
9 {
10 Thread.Sleep(1000);
11 foreach( int item in elements){
12 Console.WriteLine("Item (" + item + " ) ");
13 Thread.Sleep(1000);
14 }
15
16 }
17
18 public void Add()
19 {
20 Thread.Sleep(1500);
21 elements.Add(30);
22 }
23
24 }
25
26
27 ThreadCollection coll = new ThreadCollection();
28 Thread thread3 = new Thread(
29 new ThreadStart(coll.Run)
30 );
31 Thread thread4 = new Thread(
32 new ThreadStart(coll.Add)
33 );
34
35 thread3.Start();
36 thread4.Start();
37
這個時候,系統會拋出一個InvalidOperationException的異常。顯然,我們需要修改程式,一種方法是擷取elments的一個鏡像(snapshot),對鏡像(snapshot)進行操作,這個時候,資料內容被擷取出來,用到的類為:
System.Collections.ObjectModel.ReadOnlyCollection。將原先的foreach語句修改為:
foreach(int item in new ReadOnlyCollection<int>(elements)){...}
這種方法是對elements中的元素進行標記,elements中不允許插入資料,因此系統仍舊會爆出錯誤。徹底的解決方案是使用lock鎖塊語句,使用lock將需要進行鎖的變數傳遞進來,然後用{}操作需要lock的語句。範例程式碼如下:
代碼 1 lock(elements){
2 foreach (int item in elements)
3 {
4 Console.WriteLine("Item (" + item + " ) ");
5 Thread.Sleep(1000);
6 }
7 }
8
9 ///////////////////////////////////////////////////////////
10
11 lock(elements)
12 {
13 elements.Add(30);
14 }
此外,將elements資料拷貝出來,然後對拷貝出來的資料進行遍曆,這個時候,由於寫入和讀資料是兩個完全不同的對象,不存在同步問題,因此,也可以很好運行:
代碼1 int[] items;
2 lock(elements){
3 items = elements.ToArray();
4 }
5 foreach (int item in items)
6 {
7 Console.WriteLine("Item (" + item + " ) ");
8 Thread.Sleep(1000);
9 }
顯然,這種方法,在資料量較大的情況i下,拷貝的時候需要花費較長的時間,並且需要佔據更多的記憶體。但這種方法容易維護與理解。有時候,這種方法也非常有效,因為拷貝的時候,可能還沒有寫入較多的資料,資料仍舊在後台進行寫入,但前台已經可以對部分這些資料進行操作,而不會明顯影響後台操作資料。