c#线程学习笔记三---线程同步
线程的同步,通过WaitHandle类,及其继承EventWaitHandle类,及其以下的ManualResetEvent(手动重置)和AutoResetEvent(自动重置)事件来完成线程的同步功能
MSDN:WaitHandle:http://msdn.microsoft.com/zh-cn/library/fss7k4e9
EventWaitHandle:http://msdn.microsoft.com/zh-cn/library/73zz66k2
ManualResetEvent:http://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent
AutoResetEvent:http://msdn.microsoft.com/zh-cn/library/system.threading.autoresetevent
ManualResetEvent是手动重置信号状态,AutoResetEvent是自动重置信号状态,当一开始利用
ManualResetEvent 事件对象 = new ManualResetEvent(false); AutoResetEvent 事件对象=new AutoResetEvent(false);
将构造函数的参数设为false,即为无信号状态,在线程的执行中遇到
事件对象.WaitOne();
当前线程就会受到阻止,而停止运行,这时可以通过调用
事件对象.Set();
来激活事件对象的信号发送,从而WaitOne()方法收到信号后可以继续执行
但是,由于ManualResetEvent是手动重置信号状态,AutoResetEvent是自动重置信号状态,因此要恢复默认状态,ManualResetEvent对象必须调用
事件对象.Reset();
而AutoResetEvent对象,在本次事件执行完毕后即恢复无信号状态
代码例举:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace 线程同步3 { class Program { static ManualResetEvent mre = new ManualResetEvent(false);//声明手动ManualResetEvent对象 static void Main(string[] args) { Thread mt = new Thread(myprocess); mt.Start();//开始执行线程 for (int i = 0; i < 10; i++) { Console.ReadLine();//每按一次键盘,再将状态设为发送信号 mre.Set();//设置为发信状态 } } static void myprocess() { while (true) { Console.WriteLine("执行中"); mre.WaitOne();//等待事件发信 Console.WriteLine("执行结束"); mre.Reset();//由于是手动事件,这里每次要将线程的状态重置为无信号 } } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace 线程同步4 { class Program { static AutoResetEvent are = new AutoResetEvent(false); static void Main(string[] args) { Thread mt = new Thread(myprocess); mt.Start(); for (int i = 0; i < 10; i++) { Console.ReadLine(); are.Set(); } } static void myprocess() { while (true) { Console.WriteLine("执行中"); are.WaitOne(); Console.WriteLine("执行结束");//由于是自动事件,无需再手动调用Reset()来重置状态 } } } }
以下展示的是一个关于线程同步的实例,利用同步事件和信号状态来控制队列数据的添加和删除
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Collections; namespace 线程同步2 { class Program { static void Main(string[] args) { SyncEvents syncEvents = new SyncEvents(); Queue<int> queue = new Queue<int>();//创建一个先进先出的数据队列 Console.WriteLine("配置工作线程..."); Producer producer = new Producer(queue, syncEvents);//创建新增元素类对象,并以队列和同步事件对象初始化 Consumer consumer = new Consumer(queue, syncEvents);//创建消耗元素类对象,并以相同的队列和同步事件对象初始化 Thread pt = new Thread(producer.ThreadRun);//为对象的处理事件创建线程 Thread ct = new Thread(consumer.ThreadRun); Console.WriteLine("启动线程...."); pt.Start();//启动线程 ct.Start(); for (int i = 0; i < 4; i++) { Thread.Sleep(2500);//这里每隔2.5秒显示一次队列数列中的数据 showQueueContents(queue); } Console.WriteLine("终止线程"); syncEvents.ExitThreadEvent.Set();//为同步事件对象的退出(手动)事件,设置信号 Console.WriteLine("主线程等待其余线程执行完毕"); pt.Join();//用Join方法等待线程执行完毕 ct.Join(); } static void showQueueContents(Queue<int> q) { lock (((ICollection)q).SyncRoot)//对于数据的遍历,也要为其加上lock,以防止其他线程对其访问修改 { foreach (int i in q) { Console.Write("{0} ", i); } } Console.WriteLine(); } } class SyncEvents//同步事件类 { public SyncEvents()//构造函数,初始化事件对象 { _newItemEvent = new AutoResetEvent(false);//将事件对象均设置为无信号,并将新增设为自动重置事件、退出设置为手动重置事件 _exitThreadEvent = new ManualResetEvent(false); _eventArray = new WaitHandle[2]; _eventArray[0] = _newItemEvent; _eventArray[1] = _exitThreadEvent; } private EventWaitHandle _newItemEvent;//新建对象事件 private EventWaitHandle _exitThreadEvent;//退出线程事件 private WaitHandle[] _eventArray;//包含以上2个事件的事件队列 public EventWaitHandle ExitThreadEvent { get { return _exitThreadEvent; } } public EventWaitHandle NewItemEvent { get { return _newItemEvent; } } public WaitHandle[] EventArray { get { return _eventArray; } } } class Producer { public Producer(Queue<int> q, SyncEvents e)//以事件对象和同步事件对象作为引用对象传入 { _queue = q; _syncEvent = e; } private Queue<int> _queue; private SyncEvents _syncEvent; public void ThreadRun() { int count = 0;//执行次数计数器 int r = 0; while (!_syncEvent.ExitThreadEvent.WaitOne(0, false))//当同步对象中的退出(手动)事件没有收到信号时继续执行 { lock (((ICollection)_queue).SyncRoot) { while (_queue.Count < 20) { _queue.Enqueue(r++);//向队列中添加元素 _syncEvent.NewItemEvent.Set();//通知触发同步对象中的新增(自动)事件来发送信号 count++;//执行次数计数器 } } } Console.WriteLine("制作线程:制造了{0}个",count); } } class Consumer { public Consumer(Queue<int> q, SyncEvents e) { _queue = q; _syncEvent = e; } private Queue<int> _queue; private SyncEvents _syncEvent; public void ThreadRun() { int count = 0;//计数器 while (WaitHandle.WaitAny(_syncEvent.EventArray) != 1)//这里利用WaitHandle的WaitAny来判断事件队列哪个事件收到信号,只要不是退出(手动)事件收到信号,即可继续执行 { lock (((ICollection)_queue).SyncRoot) { _queue.Dequeue();//从队列中去除一个元素 } count++; } Console.WriteLine("消费线程:消耗了{0}个",count); } } }
由以上代码可见,本例利用一个集合了AutoResetEvent和ManualResetEvent的类型对象来控制线程同步,
在新增对象的ThreadRun方法中,利用ManualResetEvent的类型对象的WaitOne(0, false)方法来判读主线程是否给出退出信号,注意这里一用WaitOne方法的一个重载,其中设置第一个参数为所要等待事件发出信号时间
如果事件发信号超时,则直接继续执行,设为0则表示直接判断后无论是否收信直接继续执行
在消耗对象的ThreadRun方法中,利用同步对象中集合的AutoResetEvent和ManualResetEvent的一个事件队列,通过WaitHandle.WaitAny(WaitHandle[] waitHandles)方法来判断获取事件信号的对象是哪一个,并返回
队列索引值,这里在未收信时会一直处于等待状态,每当新增对象的ThreadRun方法新增对象后,会通过调用AutoResetEvent类型对象Set()方法给WaitHandle.WaitAny发信,从而使得消耗线程继续得以执行
从而,只要主线程不调用同步事件中退出事件的Set()来发信,那么每当新增线程执行,并调用新增事件的Set()方法时触发消耗线程的执行
最后执行结果为