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()方法才可以使線程任務繼續執行。

    (除引用內容外,介紹理解內容均為原創,轉載請注明出處)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM