C#中線程的終止可以利用線程的abort()或是Interrupt()函數,但是這兩個函數的缺點是關閉的不夠優雅,也就是說不管線程在做什么,都直接進行關閉操作。
而msdn上面給我們提供了一個優雅的關閉線程的方式,可以讓線程函數通過判斷自行退出線程。
代碼如下:
1 using System; 2 using System.Threading; 3 4 public class Worker 5 { 6 // This method will be called when the thread is started. 7 public void DoWork() 8 { 9 while (!_shouldStop) 10 { 11 Console.WriteLine("worker thread: working..."); 12 } 13 Console.WriteLine("worker thread: terminating gracefully."); 14 } 15 public void RequestStop() 16 { 17 _shouldStop = true; 18 } 19 // Volatile is used as hint to the compiler that this data 20 // member will be accessed by multiple threads. 21 private volatile bool _shouldStop; 22 } 23 24 public class WorkerThreadExample 25 { 26 static void Main() 27 { 28 // Create the thread object. This does not start the thread. 29 Worker workerObject = new Worker(); 30 Thread workerThread = new Thread(workerObject.DoWork); 31 32 // Start the worker thread. 33 workerThread.Start(); 34 Console.WriteLine("main thread: Starting worker thread..."); 35 36 // Loop until worker thread activates. 37 while (!workerThread.IsAlive); 38 39 // Put the main thread to sleep for 1 millisecond to 40 // allow the worker thread to do some work: 41 Thread.Sleep(1); 42 43 // Request that the worker thread stop itself: 44 workerObject.RequestStop(); 45 46 // Use the Join method to block the current thread 47 // until the object's thread terminates. 48 workerThread.Join(); 49 Console.WriteLine("main thread: Worker thread has terminated."); 50 } 51 }
輸出的結果為:
main thread: starting worker thread...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: terminating gracefully...
main thread: worker thread has terminated
分析上面的代碼可以知道,通過在主線程中設置_shouldStop 變量,在workerThread 線程中判斷_shouldStop變量來控制線程的結束,而在主線程中通過workerThread線程的jion()函數來等待workThread線程的結束。
正常情況下通過上述方法就能夠成功而優雅的結束一個線程,但是在寫程序時遇到一個問題。
非UI線程代碼如下:
1 /// <summary> 2 /// 軌跡回放的線程 3 /// </summary> 4 private void TrialReplayService() 5 { 6 mapControl1.Map.TrackingLayer.Clear();//清空跟蹤圖層中的內容 7 GlobalVariable.m_BusID.Clear(); 8 9 MapsRun mapRun = new MapsRun(workspace1, mapControl1); 10 // Create an instance of StreamReader to read from a file. 11 // The using statement also closes the StreamReader. 12 using (StreamReader sr = new StreamReader("RecordFile.txt")) 13 { 14 String line; 15 16 while ((line = sr.ReadLine()) != null&&!m_IsStop) 17 { 18 StringToBusInfo businfo = new StringToBusInfo(line); 19 string carNum = businfo.Get_CarNum(); 20 if (carNum == m_SelectCar) 21 { 22 mapRun.ShowRealInfo(line); 23 this.dataGridView2.Invoke(new InsertDataGridViewHandler(InsertDataGridView), line); 24 Thread.Sleep(50); 25 } 28 } 29 } 30 31 }
而主線程中啟動和結束TrialReplayService線程(就是利用上面的代碼創建的線程)的代碼如下:
1 /// <summary> 2 /// 啟動軌跡回放的功能 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void button1_Click(object sender, EventArgs e) 7 { 8 m_threadMonitor = new Thread(new ThreadStart(m_TrailPlayback.TrailPlaybackService)); 9 m_threadMonitor.Start(); 10 } 11 12 /// <summary> 13 /// 窗體的結束時的操作 14 /// </summary> 15 /// <param name="sender"></param> 16 /// <param name="e"></param> 17 private void TrailPlaybackForm_FormClosing(object sender, FormClosingEventArgs e) 18 { 19 20 if (m_threadMonitor.IsAlive) 21 { 22 m_TrailPlayback.RequestStop(); 23 24 m_threadMonitor.Join(); 27 } 28 31 mapControl1.Dispose(); 32 workspace1.Dispose(); 33 34 //初始化主窗體中的地圖 35 MapsRun.InitialMap(mainWorkSpace, mainMapControl); 36 }
這些代碼為項目中摘取,因此可能有些變量沒有定義。代碼8~9行初始化了一個線程變量m_threadMonitor,並且啟動了該線程。20~24行,我們試圖關閉m_threadMonitor線程,首先通過線程的IsAlive來判斷線程是否存活,如果存活,則通過自定義的RequestStop()函數來設置變量來終止線程,而24行,則是等待線程的結束。而這時產生的問題是程序卡在jion()函數的位置,不再向下執行。
經過將近一天的調試終於解決了問題。首先通過不斷的調試(雖然此時已經不能通過單步調試定位到具體的錯誤位置,但是可以通過排除法來定位,首先單獨創建一個程序來測試這種終止線程的方法是否正確,判斷后是正確的,然后開始測試循環中的各個語句,看是因為哪條語句的原因造成了這種錯誤),將問題定位到了:this.dataGridView2.Invoke(new InsertDataGridViewHandler(InsertDataGridView), line)這條語句。如果給這條語句注釋了,線程則能夠正常關閉,否則關閉線程時會卡死在UI線程jion()函數的位置。
確定了是這條語句的原因后,查詢關於control.Invole()的資料,找到了問題的原因。control的Invoke()操作是多線程操作控件的一種方式,為了線程的安全性,通過Invoke調用的函數仍然是在UI線程中執行的,而由於invoke的特性m_threadMonitor線程在調用invoke函數時會發生阻塞,直到invoke調用的函數返回后,才會繼續執行invoke函數后面的代碼。而問題的產生在於,當我們想結束線程時,執行到UI線程中的代碼 m_threadMonitor.Join()時,它在等待線程m_threadMonitor的結束,而m_threadMonitor線程在等待Invoke函數的返回,Invoke函數需要再UI線程中執行,而此時UI線程在執行m_threadMonitor.Join()代碼,因此Invoke函數在等待m_threadMonitor.Join()的結束。這樣就形成了一個死循環,m_threadMonitor.Join()等待m_threadMonitor結束,m_threadMonitor等待invoke返回,invoke等待m_threadMonitor.Join()的結束,因此代碼會卡死在這里。
解決這個問題的方法是使用control.BeginInvoke()函數,因為它是異步的操作,也就是說m_threadMonitor線程在調用BeginInvoke函數后不會等待它調用函數的返回,而是直接執行BeginInvoke后面的代碼,這樣就不會產生死循環,也就能夠順利的關閉m_threadMonitor線程了。