更新:2007 年 11 月
下面的示例演示使用 lock 关键字以及
该示例创建两个辅助线程。一个线程生成元素并将它们存储在非线程安全的泛型队列中。有关更多信息,请参见
除了只是使用 lock 关键字来防止同时访问以外,还可以用两个事件对象提供进一步的同步。一个事件对象用来通知辅助线程终止,另一个事件对象由制造者线程用来在有新项添加到队列中时通知使用者线程。这两个事件对象封装在一个名为 SyncEvents 的类中。这使事件可以轻松传递给表示制造者线程和使用者线程的对象。SyncEvents 类按如下方式定义:
C# | 复制代码 |
---|---|
public class SyncEvents { public SyncEvents() { _newItemEvent = new AutoResetEvent(false); _exitThreadEvent = new ManualResetEvent(false); _eventArray = new WaitHandle[2]; _eventArray[0] = _newItemEvent; _eventArray[1] = _exitThreadEvent; } public EventWaitHandle ExitThreadEvent { get { return _exitThreadEvent; } } public EventWaitHandle NewItemEvent { get { return _newItemEvent; } } public WaitHandle[] EventArray { get { return _eventArray; } } private EventWaitHandle _newItemEvent; private EventWaitHandle _exitThreadEvent; private WaitHandle[] _eventArray; } |
对“新项”事件使用
SyncEvents 类创建两个事件,并将它们以两种不同的形式存储:一种是作为
使用者线程和制造者线程分别由名为 Consumer 和 Producer 的类表示。这两个类都定义了一个名为 ThreadRun 的方法。这些方法用作 Main 方法创建的辅助线程的入口点。
Producer 类定义的 ThreadRun 方法如下所示:
C# | 复制代码 |
---|---|
// Producer.ThreadRun public void ThreadRun() { int count = 0; Random r = new Random(); while (!_syncEvents.ExitThreadEvent.WaitOne(0, false)) { lock (((ICollection)_queue).SyncRoot) { while (_queue.Count < 20) { _queue.Enqueue(r.Next(0,100)); _syncEvents.NewItemEvent.Set(); count++; } } } Console.WriteLine("Producer thread: produced {0} items", count); } |
此方法一直循环,直到“退出线程”事件变为终止状态。此事件的状态由
在“退出线程”事件终止前,Producer.ThreadStart 方法将尝试在队列中保留 20 项。项只是 0 到 100 之间的一个整数。在添加新项前,必须锁定该集合,以防止使用者线程和主线程同时访问该集合。这一点是使用 lock 关键字完成的。传递给 lock 的参数是通过
Consumer 对象还定义名为 ThreadRun 的方法。与制造者的 ThreadRun 类似,此方法由 Main 方法创建的辅助线程执行。然而,使用者的 ThreadStart 必须响应两个事件。Consumer.ThreadRun 方法如下所示:
C# | 复制代码 |
---|---|
// Consumer.ThreadRun public void ThreadRun() { int count = 0; while (WaitHandle.WaitAny(_syncEvents.EventArray) != 1) { lock (((ICollection)_queue).SyncRoot) { int item = _queue.Dequeue(); } count++; } Console.WriteLine("Consumer Thread: consumed {0} items", count); } |
此方法使用
Main 方法首先创建一个队列(该队列的内容将被生成和使用)和 SyncEvents 的一个实例(已在前面演示):
C# | 复制代码 |
---|---|
Queue<int> queue = new Queue<int>(); SyncEvents syncEvents = new SyncEvents(); |
然后,Main 配置 Producer 和 Consumer 对象以供辅助线程使用。然而,此步骤并不创建或启动实际的辅助线程:
C# | 复制代码 |
---|---|
Producer producer = new Producer(queue, syncEvents); Consumer consumer = new Consumer(queue, syncEvents); Thread producerThread = new Thread(producer.ThreadRun); Thread consumerThread = new Thread(consumer.ThreadRun); |
请注意,队列和同步事件对象作为构造函数参数同时传递给 Consumer 和 Producer 线程。这提供了两个对象,它们具有执行各自任务所需的共享资源。然后创建两个新的
接着,Main 通过调用
C# | 复制代码 |
---|---|
producerThread.Start(); consumerThread.Start(); |
此时,创建了两个新的辅助线程,它们独立于当前正在执行 Main 方法的主线程开始异步执行过程。事实上,Main 接下来要做的事情是通过调用
C# | 复制代码 |
---|---|
for (int i=0; i<4; i++) { Thread.Sleep(2500); ShowQueueContents(queue); } |
最后,Main 通过调用“退出线程”事件的
有一个线程同步的最终示例:ShowQueueContents 方法。与制造者线程和使用者线程类似,此方法使用 lock 获得对队列的独占访问权限。然而在这种情况下,独占访问非常重要,因为 ShowQueueContents 对整个集合进行枚举。对集合进行枚举是一个特别容易由于异步操作而造成数据损坏的操作,因为它需要遍历整个集合的内容。
请注意,ShowQueueContents 是由主线程执行的,因为它被 Main 调用。这意味着,当此方法获得对项队列的独占访问权限时,既阻止了制造者线程访问队列,也阻止了使用者线程访问队列。ShowQueueContents 锁定队列并枚举其内容:
C# | 复制代码 |
---|---|
private static void ShowQueueContents(Queue<int> q) { lock (((ICollection)q).SyncRoot) { foreach (int item in q) { Console.Write("{0} ", item); } } Console.WriteLine(); } |
下面是完整的示例。
示例
C# | 复制代码 |
---|---|
using System; using System.Threading; using System.Collections; using System.Collections.Generic; public class SyncEvents { public SyncEvents() { _newItemEvent = new AutoResetEvent(false); _exitThreadEvent = new ManualResetEvent(false); _eventArray = new WaitHandle[2]; _eventArray[0] = _newItemEvent; _eventArray[1] = _exitThreadEvent; } public EventWaitHandle ExitThreadEvent { get { return _exitThreadEvent; } } public EventWaitHandle NewItemEvent { get { return _newItemEvent; } } public WaitHandle[] EventArray { get { return _eventArray; } } private EventWaitHandle _newItemEvent; private EventWaitHandle _exitThreadEvent; private WaitHandle[] _eventArray; } public class Producer { public Producer(Queue<int> q, SyncEvents e) { _queue = q; _syncEvents = e; } // Producer.ThreadRun public void ThreadRun() { int count = 0; Random r = new Random(); while (!_syncEvents.ExitThreadEvent.WaitOne(0, false)) { lock (((ICollection)_queue).SyncRoot) { while (_queue.Count < 20) { _queue.Enqueue(r.Next(0,100)); _syncEvents.NewItemEvent.Set(); count++; } } } Console.WriteLine("Producer thread: produced {0} items", count); } private Queue<int> _queue; private SyncEvents _syncEvents; } public class Consumer { public Consumer(Queue<int> q, SyncEvents e) { _queue = q; _syncEvents = e; } // Consumer.ThreadRun public void ThreadRun() { int count = 0; while (WaitHandle.WaitAny(_syncEvents.EventArray) != 1) { lock (((ICollection)_queue).SyncRoot) { int item = _queue.Dequeue(); } count++; } Console.WriteLine("Consumer Thread: consumed {0} items", count); } private Queue<int> _queue; private SyncEvents _syncEvents; } public class ThreadSyncSample { private static void ShowQueueContents(Queue<int> q) { lock (((ICollection)q).SyncRoot) { foreach (int item in q) { Console.Write("{0} ", item); } } Console.WriteLine(); } static void Main() { Queue<int> queue = new Queue<int>(); SyncEvents syncEvents = new SyncEvents(); Console.WriteLine("Configuring worker threads..."); Producer producer = new Producer(queue, syncEvents); Consumer consumer = new Consumer(queue, syncEvents); Thread producerThread = new Thread(producer.ThreadRun); Thread consumerThread = new Thread(consumer.ThreadRun); Console.WriteLine("Launching producer and consumer threads..."); producerThread.Start(); consumerThread.Start(); for (int i=0; i<4; i++) { Thread.Sleep(2500); ShowQueueContents(queue); } Console.WriteLine("Signaling threads to terminate..."); syncEvents.ExitThreadEvent.Set(); producerThread.Join(); consumerThread.Join(); } } |
复制代码 | |
---|---|
Configuring worker threads... Launching producer and consumer threads... 22 92 64 70 13 59 9 2 43 52 91 98 50 96 46 22 40 94 24 87 79 54 5 39 21 29 77 77 1 68 69 81 4 75 43 70 87 72 59 0 69 98 54 92 16 84 61 30 45 50 17 86 16 59 20 73 43 21 38 46 84 59 11 87 77 5 53 65 7 16 66 26 79 74 26 37 56 92 Signalling threads to terminate... Consumer Thread: consumed 1053771 items Producer thread: produced 1053791 items |