代码改变世界

C#多线程技术(二)

2010-02-22 01:06  ubunoon  阅读(375)  评论(0编辑  收藏  举报

上一篇中讲述了简单的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             foreachint 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下,拷贝的时候需要花费较长的时间,并且需要占据更多的内存。但这种方法容易维护与理解。有时候,这种方法也非常有效,因为拷贝的时候,可能还没有写入较多的数据,数据仍旧在后台进行写入,但前台已经可以对部分这些数据进行操作,而不会明显影响后台操作数据。