C#中線程的終止問題


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線程了。

 

 


免責聲明!

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



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