C# “Thread类Suspend()与Resume()已过时” 解决方法(利用ManualResetEvent类)

     近日用C#在项目中需要多线程编程时为了挂起与恢复线程使用了Thread类的Suspend()与Resume()方法,可是VS提示这两个方法已经过时了(过时原因微软的官方文档中有介绍:https://msdn.microsoft.com/en-us/library/system.threading.thread.suspend(v=vs.110).aspx ),主要是由于Suspend方法的挂起点难以确定,容易造成线程的锁死。不得已查找了相关的资料发现很多文档都介绍的不全面,缺少了很多介绍性的说明,所以整合起来记录一下。

      项目中的需要也很简单,就是新开一个线程不断去检测当前修改的一个变量是否超时。先贴程序如下:

     using System.Threading;
  1     public delegate void TimeOutEventHandler();
  2 
  3     public interface ITimeOutThread
  4     {
  5         event TimeOutEventHandler DataTimeOutEvent;   // 超时事件
  6         void StopTimeOutCheck(); // 暂停超时检测
  7         void StartTimeOutCheck();  // 恢复超时检测
  8         void ClearTimeOutMark();   // 清除超时标志,防止超时
  9         void DisposeTimeOutCheck();  // 彻底关闭超时检测
 10 
 11         int TimeOutInterval   // 超时间隔,单位为ms
 12         {
 13             get;
 14             set;
 15         }
 16 
 17     }
 18 
 19     /// <summary>
 20     /// 超时检测线程
 21     /// 运行方式:
 22     /// 1. 设置超时时间,默认超时时间为 200ms
 23     /// 2. 通过ResumeTimeOutCheck()来启动超时检测, SuspendTimeOutCheck()来暂停超时检测
 24     /// 3. 启动超时检测后,通过ClearTimeOutMark()来不断清除超时检测,防止其超时,通过SetTimeOutMark()来触发超时事件
 25     /// 4. 超时事件为DataTimeOutEvent, 通过绑定超时事件处理函数来处理超时事件(超时事件发出后不暂停超时检测,这意味这需要手动暂停)
 26     /// 5. 在线程使用完毕后一定要停止超时,停止后超时检测将直接停止(不可恢复)
 27     /// </summary>
 28     class TimeOutThread:ITimeOutThread
 29     {
 30         enum m_ThreadState
 31         {
 32             Stopped = 0,
 33             Started,
 34             Suspended,
 35             Resumed,
 36         };
 37 
 38         private int timeOutMark;   // 超时标志
 39         private m_ThreadState stateOfThread;   // 线程运行状态
 40         private Thread checkMarkThread;        // 检测超时线程
 41         private object criticalAraeLockItem;   // 线程安全锁变量
 42         private bool threadControl;            // 线程停止循环运行标志
 43 
 44         private ManualResetEvent manualResetEvent;   // 线程控制事件, 当被手动Reset()则WaitOne()会使线程阻塞
 45                                                      // 当被Set()便再不会被阻塞直到Reset()
 46                                                      // 在本类中,当停止检测超时时便手动Reset()使线程阻塞,开始检测
 47                                                      // 时Set()以使线程持续执行
 48 
 49         private int timeOutInterval;    // 超时时间
 50         public int TimeOutInterval
 51         {
 52             get
 53             {
 54                 return timeOutInterval;
 55             }
 56 
 57             set
 58             {
 59                 timeOutInterval = value;
 60             }
 61         }
 62 
 63         public event TimeOutEventHandler DataTimeOutEvent;
 64 
 65         /// <summary>
 66         /// 构造函数, 通过设置超时时间初始化
 67         /// </summary>
 68         /// <param name="timeOutTime"></param>
 69         public TimeOutThread(int timeoutTime)
 70         {
 71             this.criticalAraeLockItem = new object();
 72             this.threadControl = true;
 73             this.timeOutInterval = timeoutTime;
 74             this.ClearTimeOutMark();
 75             this.stateOfThread = m_ThreadState.Suspended;
 76             this.checkMarkThread = new Thread(new ThreadStart(this.CheckTimeOutMark));
 77             this.manualResetEvent = new ManualResetEvent(false);  // 初始情况便阻塞以便启动线程
 78             this.checkMarkThread.Start();  // 此时虽然启动线程,但线程阻塞,不会运行
 79         }
 80 
 81         /// <summary>
 82         ///  默认构造函数,默认超时200ms
 83         /// </summary>
 84         public TimeOutThread()
 85             : this(200)
 86         {
 87             
 88         }
 89 
 90         /// <summary>
 91         /// 阻塞线程
 92         /// </summary>
 93         private void SuspendThread()
 94         {
 95             this.manualResetEvent.Reset();
 96         }
 97 
 98         /// <summary>
 99         /// 恢复线程
100         /// </summary>
101         private void ResumeThread()
102         {
103             this.manualResetEvent.Set();
104         }
105 
106         /// <summary>
107         /// 启动超时检测线程
108         /// </summary>
109         public void StartTimeOutCheck()
110         {
111             if (this.stateOfThread == m_ThreadState.Suspended) // 线程已启动但是被挂起
112             {
113                 // 恢复线程
114                 this.ResumeThread();
115             }
116 
117             // 更新状态
118             this.stateOfThread = m_ThreadState.Resumed;
119         }
120 
121         /// <summary>
122         /// 停止超时检测线程
123         /// </summary>
124         public void StopTimeOutCheck()
125         {
126             if (this.stateOfThread == m_ThreadState.Resumed)
127             {
128                 this.SuspendThread();
129                 this.stateOfThread = m_ThreadState.Suspended;
130             }
131         }
132         
133         /// <summary>
134         /// 彻底停止超时检测
135         /// </summary>
136         public void DisposeTimeOutCheck()
137         {
138             this.threadControl = false;  // 停止线程循环
139             this.ResumeThread();
140             this.stateOfThread = m_ThreadState.Suspended;
141 
142             try
143             {
144                 this.checkMarkThread.Abort();
145             }
146             catch (Exception)
147             {
148 
149             }
150         }
151         /// <summary>
152         /// 检测超时标记是否已经被清除
153         /// </summary>
154         /// <returns></returns>
155         private bool IsTimeOutMarkCleared()
156         {
157             if (this.timeOutMark == 1)
158                 return true;
159             else
160                 return false;
161         }
162 
163         /// <summary>
164         /// 清除超时标记
165         /// </summary>
166         public void ClearTimeOutMark()
167         {
168             lock(this.criticalAraeLockItem)
169             {
170                 this.timeOutMark = 1;  // 清除超时标记
171             }
172         }
173         
174 
175         /// <summary>
176         /// 设置超时标记
177         /// </summary>
178         private void SetTimeOutMark()
179         {
180             lock(this.criticalAraeLockItem)
181             {
182                 this.timeOutMark = 0;  // 设置超时标记
183             }
184         }
185 
186         /// <summary>
187         /// routine work, 在threadControl不为false时不断检测timeOutMark,若有超时,则发出超时事件
188         /// </summary>
189         private void CheckTimeOutMark()
190         {
191             while (this.threadControl == true)
192             {
193                 manualResetEvent.WaitOne();  // 用以阻塞线程, 当Set()被调用后恢复,Reset()被调用后阻塞
194 
195                 Thread.Sleep(this.timeOutInterval);  // 线程睡眠超时事件长度
196 
197                 if (this.IsTimeOutMarkCleared()) // 线程超时标志已被更新,不发出超时事件
198                 {
199                     //设置超时标志, 若下次检测超时标记依旧处于被设置状态,则超时
200                     this.SetTimeOutMark();
201                 }
202                 else
203                 {
204                     // 超时标志未被清除并且未被要求停止检测超时,发出超时事件
205                     if ((DataTimeOutEvent != null) && (this.stateOfThread == m_ThreadState.Resumed))
206                     {
207                         DataTimeOutEvent.Invoke();
208                     }
209                 }
210             }
211 
212         }
213 
214         /// <summary>
215         /// 析构函数
216         /// </summary>
217         ~TimeOutThread()
218         {
219             this.DisposeTimeOutCheck(); // 彻底停止线程
220         }
221     }

      开始运行线程后 ,不断检测timeOutMark变量在线程休眠的期间是否被重置过,即是否调用了接口中的ClearTimeOutMark()方法。当在规定的timeoutInterval期间调用了该方法后发出超时事件等待处理,在处理的过程中为了节约系统资源便需要挂起线程以及恢复线程。

      在程序中使用了ManualResetEvent类,此类用于进行线程安全的线程通讯(System.Threading)。在应用中一般只需要使用ManualResetEvent类的四个方法:构造函数,Set(), Reset(), 以及WaitOne().

     此处引用(http://www.cnblogs.com/modify/archive/2013/01/10/2855014.html)的博客中关于此类的介绍:

     在看介绍理解前可以先假设ManualResetEvent类中有一个bool类型的变量A

     1、初始化:public ManualResetEvent(bool initialState);

  ManualResetEvent的构造方法有个bool型参数,当为true时,则表示有信号(该变量A初始状态为true, 处于Set状态),为false时,则表示无信号(即该变量A为false, 处于ReSet状态)。这个怎么理解呢?我们接着看ManualResetEvent3个基本方法中的WaitOne方法。

     2、WaitOne方法:WaitOne方法有几种4种重载,我在这里只对它的功能进行分析。

  WaitOne方法,顾名思义,它会具有一种等待的功能,也就是线程阻塞。这里的阻塞功能是有条件的,当无信号(ManualResetEvent对象的A变量为false, 状态为Reset)时,它是阻塞的,有信号时(A变量为true,状态为Set),它将无任何阻塞,被执行时就直接跳过了(这个从逻辑上应该挺好理解:当有信号需要处理时,需要立即处理,没有任何信号时,就当然要等一等了)。所以,回顾到1,当初始化ManualResetEvent时,initialState为false,WaitOne将会有阻塞效果,否则,没有阻塞效果。

     3、Set方法:将ManualResetEvent对象的信号状态设为有信号状态,这个时候WaitOne如果正在阻塞中的话,将会立即终止阻塞,向下继续执行。而且这个状态一直不变的话,每次执行到WaitOne都将无任何阻塞。

     4、Reset方法:将ManualResetEvent对象的信号状态设为无信号状态,当下次执行到WaitOne时,又将重新开始阻塞。

     这里对应这些操作到需要Suspend与Resume的线程上来,首先可以先将ManualResetEvent对象的WaitOne()方法放在该线程需要循环执行的函数内部(一般应该放在开头,这样可以使线程的挂起恢复的位置都在线程任务的初始点)。需要Suspend功能时将调用ManualResetEvent对象的Reset()方法, 这时线程任务运行至WaitOne()语句就会阻塞,起到了挂起线程的作用。当需要Resume时,调用Set()方法,WaitOne()便不再阻塞向下执行直到下次调用Reset()方法。这种做法实际上就避免了Suspend在程序中不知道挂起位置在哪里的弊端,使得挂起位置始终处于WaitOne()方法调用的位置。

    需要再提一下的是manualResetEvent在初始化时的bool参数,当该参数为true时,manualResetEvent对象在开始时便处于Set状态,WaitOne()不会阻塞,当该参数为false时, manualResetEvent对象在开始时处于Reset()状态,第一次调用WaitOne()方法时便会使线程阻塞,直到第一次调用Reset()方法才可以使线程任务继续执行。

    (除引用内容外,介绍理解内容均为原创,转载请注明出处)

posted @ 2018-05-13 22:14  花花世界1202  阅读(10491)  评论(0编辑  收藏  举报