C# 多线程通信详解
一、WaitHandler的类层次
可以看到 WaitHandle是 事件(EventWaitHandle)、互斥体(Mutex)、信号量(Sempahore)的父类。
WaitHandle我们最经常使用的方法,并是使用它的静态方法WaitAll. 我们会发现在这个WaitHandle里面只有等待方法,也就是它会阻塞当前线程的执行。
那么如何要解除它对当前线程的阻塞呢,那么就需要依赖于各个子类的方法了。
例如现在有一个这样的场景,如何在一个方法中,等待所有的线程全部执行完,最后再统计得到的计算结果呢?
WaitHandle[] handlers = new WaitHandle[]{ new AutoResetEvent(false), new AutoResetEvent(false), new AutoResetEvent(false), new AutoResetEvent(false), new AutoResetEvent(false), new AutoResetEvent(false), new AutoResetEvent(false), new AutoResetEvent(false) }; for (var i = 0; i < handlers.Length; i++) { ThreadPool.QueueUserWorkItem(ar => { int index = (int)ar; Thread.Sleep(1000); AppCenter.AppendLog("任务:" + index + "开始执行!"); (handlers[index] as AutoResetEvent).Set(); }, i); } ThreadPool.QueueUserWorkItem(ar => { WaitHandle.WaitAll(handlers); AppCenter.AppendLog("所有任务都已经完成了,我不用再等待了。"); });
二、EventWaitHandle
这个方法,可以方便实现两个线程之间的相互通信。
如何实现两个线程的相互通信?
EventWaitHandle handleA = new AutoResetEvent(false); EventWaitHandle handleB = new AutoResetEvent(false); ThreadPool.QueueUserWorkItem(ar => { AppCenter.AppendLog("A:我是A,我已经开始运行了"); Thread.Sleep(2000); AppCenter.AppendLog("A:我想睡觉了,B你先跑跑吧。"); EventWaitHandle.SignalAndWait(handleB, handleA); AppCenter.AppendLog("A:开始工作ing"); Thread.Sleep(3000); AppCenter.AppendLog("A:这个有点难,问下B"); EventWaitHandle.SignalAndWait(handleB, handleA); AppCenter.AppendLog("A:不错,今天任务搞定,我也闪人了。"); }); ThreadPool.QueueUserWorkItem(ar => { handleB.WaitOne(); AppCenter.AppendLog("B:我是B,我已经顶替A开始运行了。"); Thread.Sleep(5000); AppCenter.AppendLog("B:我的事情已经做完了,该让A搞搞了,休息一会。"); EventWaitHandle.SignalAndWait(handleA, handleB); AppCenter.AppendLog("B:hi,A我搞定了,下班了。"); handleA.Set(); });
那么AutoResetEvent和ManualResetEvent有什么区别呢?我们先做个实验。
private EventWaitHandle manualEvent = new ManualResetEvent(false); private void ManualResetEvent_Click(object sender, EventArgs e) { AppCenter.CleanLogs(); ThreadPool.QueueUserWorkItem(ar => { int i = 0; while (true) { manualEvent.WaitOne(); //ManualResetEvent的Set()方法,让事件的终止状态永远为true,让这里一直能执行。 i++; //而AutoResetEvent的Set()方法,初始化让这里执行一次,然后再次执行时是非终止的。将阻塞原有线程的执行 AppCenter.AppendLog("#" + i.ToString()); Thread.Sleep(1000); } }); } private void button2_Click(object sender, EventArgs e) { manualEvent.Set(); } private void button3_Click(object sender, EventArgs e) { manualEvent.Reset(); }
运行结果
我们会发现ManualResetEvent在触发Set()方法会,解除了原有的线程的 WaitOne方法,会一直打印输出。
而当我们替换为AutoResetEvent方法时候。
此时每次只会打印一个输出。因为它将 事件的状态设置为终止后,又变为了false.
三、Semaphore 控制并行线程的执行
应用场景,如果有多个线程跑,我能否每次控制3个线程一起跑呢。
Semaphore sempore = new Semaphore(0, 3); for (int i = 0; i < 8; i++) { ThreadPool.QueueUserWorkItem(ar => { sempore.WaitOne(); AppCenter.AppendLog("\t第:" +((int)ar).ToString() + "个开始运行."); },i); } ThreadPool.QueueUserWorkItem(ar => { for (int i = 0; i < 3; i++) { AppCenter.AppendLog("第" + (i + 1).ToString() + "批开始执行."); sempore.Release(3); Thread.Sleep(5000); } });
运行结果: