标签:间隔 delegate 析构 处理 code 情况 跳过 初始 abort
近日用C#在项目中需要多线程编程时为了挂起与恢复线程使用了Thread类的Suspend()与Resume()方法,可是VS提示这两个方法已经过时了(过时原因微软的官方文档中有介绍:https://msdn.microsoft.com/en-us/library/system.threading.thread.suspend(v=vs.110).aspx ),主要是由于Suspend方法的挂起点难以确定,容易造成线程的锁死。不得已查找了相关的资料发现很多文档都介绍的不全面,缺少了很多介绍性的说明,所以整合起来记录一下。
项目中的需要也很简单,就是新开一个线程不断去检测当前修改的一个变量是否超时。先贴程序如下:
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 ///
20 /// 超时检测线程
21 /// 运行方式:
22 /// 1. 设置超时时间,默认超时时间为 200ms
23 /// 2. 通过ResumeTimeOutCheck()来启动超时检测, SuspendTimeOutCheck()来暂停超时检测
24 /// 3. 启动超时检测后,通过ClearTimeOutMark()来不断清除超时检测,防止其超时,通过SetTimeOutMark()来触发超时事件
25 /// 4. 超时事件为DataTimeOutEvent, 通过绑定超时事件处理函数来处理超时事件(超时事件发出后不暂停超时检测,这意味这需要手动暂停)
26 /// 5. 在线程使用完毕后一定要停止超时,停止后超时检测将直接停止(不可恢复)
27 ///
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 ///
66 /// 构造函数, 通过设置超时时间初始化
67 ///
68 ///
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 ///
82 /// 默认构造函数,默认超时200ms
83 ///
84 public TimeOutThread()
85 : this(200)
86 {
87
88 }
89
90 ///
91 /// 阻塞线程
92 ///
93 private void SuspendThread()
94 {
95 this.manualResetEvent.Reset();
96 }
97
98 ///
99 /// 恢复线程
100 ///
101 private void ResumeThread()
102 {
103 this.manualResetEvent.Set();
104 }
105
106 ///
107 /// 启动超时检测线程
108 ///
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 ///
122 /// 停止超时检测线程
123 ///
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 ///
134 /// 彻底停止超时检测
135 ///
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 ///
152 /// 检测超时标记是否已经被清除
153 ///
154 ///
155 private bool IsTimeOutMarkCleared()
156 {
157 if (this.timeOutMark == 1)
158 return true;
159 else
160 return false;
161 }
162
163 ///
164 /// 清除超时标记
165 ///
166 public void ClearTimeOutMark()
167 {
168 lock(this.criticalAraeLockItem)
169 {
170 this.timeOutMark = 1; // 清除超时标记
171 }
172 }
173
174
175 ///
176 /// 设置超时标记
177 ///
178 private void SetTimeOutMark()
179 {
180 lock(this.criticalAraeLockItem)
181 {
182 this.timeOutMark = 0; // 设置超时标记
183 }
184 }
185
186 ///
187 /// routine work, 在threadControl不为false时不断检测timeOutMark,若有超时,则发出超时事件
188 ///
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 ///
215 /// 析构函数
216 ///
217 ~TimeOutThread()
218 {
219 this.DisposeTimeOutCheck(); // 彻底停止线程
220 }
221 }
开始运行线程后 ,不断检测timeOutMark变量在线程休眠的期间是否被重置过,即是否调用了接口中的ClearTimeOutMark()方法。当在规定的timeoutInterval期间调用了该方法后发出超时事件等待处理,在处理的过程中为了节约系统资源便需要挂起线程以及恢复线程。
在程序中使用了ManualResetEvent类,此类用于进行线程安全的线程通讯(System.IO.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变量为false,状态为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()方法才可以使线程任务继续执行。
(除引用内容外,介绍理解内容均为原创,转载请注明出处)
C# “Thread类Suspend()与Resume()已过时” 解决方法(利用ManualResetEvent类)
标签:间隔 delegate 析构 处理 code 情况 跳过 初始 abort
原文地址:https://www.cnblogs.com/15821216114sw/p/9033673.html