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