近日用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()方法才可以使線程任務繼續執行。
(除引用內容外,介紹理解內容均為原創,轉載請注明出處)