首页 > 代码库 > c#多线程之EventWaitHandle再次使用

c#多线程之EventWaitHandle再次使用

 1     /// <summary> 2     /// 文件传输器,用来获取全文文件,自动根据全文文件数量,开启一定数量的线程,采用生产者消费模式 3     /// </summary> 4     public class FileTranser 5     { 6         private static IFileTranser Transer = new RealFileTranser(); 7         // 文件队列 8         static Queue<FullTextListViewModel> FileTaskQueue = new Queue<FullTextListViewModel>(); 9         // 为保证线程安全,使用一个锁来保护队列的访问10         readonly static object locker = new object();11         // 通过 autoResetEvent 给工作线程发信号12         static EventWaitHandle autoResetEvent = new AutoResetEvent(false);13         public static CancellationTokenSource cancel = null;14 15         static int MaxThreadCount = 5;16         static int MinThreadCount = 1;17         static int PageSize = 100;18 19         static List<Thread> threads = new List<Thread>();20         static bool IsRunning = false;21         public static void Start(FullTextListViewModel model, int total)22         {23             if (cancel != null && cancel.IsCancellationRequested) return;24             if (IsRunning == false || threadCount > threads.Count)25             {26                 int startCount = threadCount - threads.Count;27                 IsRunning = true;28                 // 任务开始,启动工作线程29                 for (int i = 0; i < startCount; i++)30                 {31                     Thread t = new Thread(Work);32                     t.Name = "f_" + (i + 1);33                     t.IsBackground = false;34                     t.Start();35                     threads.Add(t);36                 }37             }38             lock (locker)39             {40                 FileTaskQueue.Enqueue(model);41             }42             autoResetEvent.Set();43         }44         public static void Cancel()45         {46             IsRunning = false;47             threads.Clear();48             if (cancel != null)49             {50                 cancel.Cancel();51             }52             autoResetEvent.Set();53             Logger.Log("取消获取全文的线程执行");54         }55         /// <summary>执行工作</summary>56         static void Work()57         {58             while (true)59             {60                 if (cancel.IsCancellationRequested)61                 {62                     Logger.Log("线程已取消,当前线程:" + Thread.CurrentThread.Name);63                     autoResetEvent.Set();64                     break;65                 }66                 else67                 {68                     FullTextListViewModel model = null;69                     lock (locker)70                     {71                         if (FileTaskQueue.Count > 0)72                         {73                             model = FileTaskQueue.Dequeue();74                         }75                     }76                     if (model != null)77                     {78                         Logger.Log("全文传输文件已经开始,当前Id:" + model.ClientTaskId + ",当前线程:" + Thread.CurrentThread.Name);79                         FileTranser.Transer.DoWork(model);80                     }81                     else82                     {83                         Logger.Log("线程" + Thread.CurrentThread.Name + ",已被阻塞,等待任务");84                         autoResetEvent.WaitOne();85                     }86                 }87             }88         }89     }

     这段不到100行的代码,采用的思想是,生产者消费模式,其中应用了AutoResetEvent ,从字面上看,是自动重置事件,它是EventWaitHandle的一个子类。

     我们还是先来看看这段代码所要表达的意思。第8行,定义了一个文件传输队列FileTaskQueue,它用来接收生产者生产的实体,即FullTextListViewModel类型的对象。29-35行,开启了若干个线程,40行,给队列加锁(进队出队必须加锁,因为多线程的缘故),把model放到队列里,紧接着,通过autoResetEvent.Set()方法,释放被阻塞的线程。此时如果有三个线程,队列里只有1个model,那么三个线程消费一个model,必然有两个线程直接被阻塞了,那个得到model的线程执行完毕后,也被阻塞了,一直到下一个model进队,这三个线程就像三名运动员跑着去搬东西,如果东西搬完了,就原地待命,听枪响,那什么时候枪响呢?如果有东西来了,那么就会开枪。于是三名运动员竭尽全力去抢东西,如果其中有一人抢到了,那么其它两人就原地待命,如果要搬运的东西比较多的话,那么这三名运动员就一直忙碌,按照先来先搬的原则,搬了就走。

      按照刚才的理解,那么AutoResetEvent,就是刚才那把枪,它有两种状态,一种是开枪状态(非终止状态) ,另一种是已经开过枪状态(终止状态)。如果已经开过枪了,那么什么时候状态会变化呢?我觉得应该是开枪后,autoResetEvent.WaitOne()阻塞的线程继续运行,然后状态自动变为开枪状态,以便下一次再开枪。WaitOne就是等待枪声,否则一直阻塞。如果此时有多个线程都在阻塞,听到枪声后,只有一个线程被释放。这点很关键,否则在设计多线程的程序时,会有意想不到的结果。

 

c#多线程之EventWaitHandle再次使用