C#语言 线程基础(2)

2、优雅的退出线程

上一节我们讲了如何建立和启动线程,那么线程应该如何退出呢?

按照要求,无论使用何种编程语言,线程都必须自然退出,而不应该被迫退出。所谓自然退出,就是线程的入口方法执行完毕退出(包括使用异常跳出方法、使用return跳出方法或令方法运行完毕),线程入口方法执行完毕,标志着线程退出,此时.net Framework会继续执行一段代码,回收线程占用的资源。

实际上,操作系统提供了方法去杀死一个线程,在Win32编程中,使用函数TerminateThread可以杀死一个线程,但这种方法并不推荐(参见《Windows核心编程》),因为这样去杀死一个线程,一方面有可能导致线程占用的资源得不到释放,另一方面,当线程被杀死的时候,我们无法得知线程正在做什么,很有可能它正在执行非常重要的代码(例如正在向磁盘写数据),突然停止线程运行会导致很多无法预料的问题。

在.net Framework中没有提供任何可以杀死线程的方法,所以我们只能等待线程执行完毕后自然死亡。

对于线程中没有无限循环(包括循环次数非常多的循环)或者阻塞操作(线程同步中详细讲解),即可以在有限时间内执行完毕的线程,编程者无需干预其退出,线程会自己执行完毕后退出;
对于线程中有无限循环(包括循环次数很多的循环)或者阻塞操作,即如果不加干预线程无法结束运行或经过很久才能结束运行,编程者需要干预其退出,通过编程令线程能够执行完毕后退出。
上一节我们使用了一种通过Thread对象的Abort方法在一个线程停止另一个线程的运行方式,但这种方法颇为暴力,具体表现为:

通过异常来处理程序的分支流程是不推荐的,应该使用if, swtich, for, while等传统语句;
一旦调用了Thread对象的Abort方法,线程立即抛出ThreadAbortException异常并通过catch块退出,此时无法得知线程代码正运行在哪一行,是否可以退出。如果线程里还有处理I/O的代码,则流是否被刷新,是否被关闭,是否写入成功等等都无法控制。
我们需要一种方法,在特定的某一行安全的代码上退出线程,从而不会引发上述的问题。

编程的方法很简单,设置一个可以在多个线程方法中都可以访问到的布尔值,线程方法中使用代码检测到这个布尔值变化后退出方法即可。

在上一节使用的代码中,做如下改动:

FormMain.cs

1   using System;
2   using System.Threading;
3   using System.Windows.Forms;
4    
5   namespace Edu.Study.Multithreading.HappyEnd {
6    
7       /// <summary>
8       /// 线程中操作文本框的委托类型
9        /// </summary>
10       /// <param name="textBox">要操作的文本框</param>
11       /// <param name="num">文本框中显示的数字</param>
12       public delegate void SetTextBoxHandler(TextBox textBox, int num);
13    
14       /// <summary>
15       /// 关闭窗口的委托类型
16       /// </summary>
17       public delegate void CloseFormHandler();
18    
19    
20       /// <summary>
21       /// 主窗体类
22       /// </summary>
23       public partial class FormMain : Form {
24    
25           private Thread thread1, thread2;
26    
27           // 用于标志线程是否继续结束的布尔值
28           private bool canThreadFinish = false;
29    
30           /// <summary>
31           /// 构造器
32           /// </summary>
33           public FormMain() {
34               InitializeComponent();
35               
36               this.thread1 = new Thread(new ParameterizedThreadStart(this.ThreadWork));
37               this.thread2 = new Thread(new ParameterizedThreadStart(this.ThreadWork));
38           }
39    
40           /// <summary>
41           /// 线程工作方法(线程入口点方法)
42           /// </summary>
43           /// <param name="arg">线程参数</param>
44           public void ThreadWork(object arg) {
45               int num = 1;
46               while (this.canThreadFinish == false) {
47                   this.Invoke(new SetTextBoxHandler(this.SetTextBox), arg, num++);
48                   Thread.Sleep(10);
49               }
50           }
51    
52           /// <summary>
53           /// 在线程中访问窗体或控件的委托方法, 符合委托类型SetTextBoxHandler
54           /// </summary>
55           /// <param name="textBox">要操作的文本框</param>
56           /// <param name="num">文本框中显示的数字</param>
57           private void SetTextBox(TextBox textBox, int num) {
58               textBox.Text = num.ToString();
59           }
60    
61           /// <summary>
62           /// 等待线程结束的线程入口方法
63           /// </summary>
64           private void WaitThreadIsClosed(object arg) {
65               // ThreadState枚举表示线程的当前状态, Running表示线程正在运行
66               if (this.thread1.ThreadState == ThreadState.Running) {
67                   // 先尝试等待1000毫秒, 看线程是否可以自然结束
68                   if (this.thread1.Join(1000) == false) {
69                       // 如果等待失败, 则强行退出线程
70                       this.thread1.Abort();
71                   }
72               }
73               if (this.thread2.ThreadState == ThreadState.Running) {
74                   if (this.thread2.Join(1000) == false) {
75                       this.thread2.Abort();
76                   }
77               }
78               // 通过委托方法关闭窗体
79               this.Invoke(new CloseFormHandler(this.CloseForm));
80           }
81    
82           /// <summary>
83           /// 关闭窗体的委托方法
84           /// </summary>
85           private void CloseForm() {
86               this.Close();
87           }
88    
89           /// <summary>
90           /// 窗体关闭事件
91           /// </summary>
92           private void FormMain_FormClosing(object sender, FormClosingEventArgs e) {
93               if (this.canThreadFinish == false) {
94                   // 修改标志变量, 令线程代码可以退出
95                   this.canThreadFinish = true;
96   
97                   // 启动线程退出等待线程
98                   new Thread(new ParameterizedThreadStart(this.WaitThreadIsClosed)).Start();
99                   
100                   // 取消关闭窗体操作
101                   e.Cancel = true;
102               }
103           }
104    
105           /// <summary>
106           /// 线程启动按钮被点击事件
107           /// </summary>
108           private void startButton_Click(object sender, EventArgs e) {
109               this.thread1.Start(this.firstTextBox);
110               this.thread2.Start(this.secondTextBox);
111               this.startButton.Enabled = false;
112           }
113       }
114   }

以上是修改后的代码,新的代码执行起来和上一节的代码执行结果相同,但当关闭窗口时,上一节代码中的两个线程立即结束,而本节介绍的代码则必然在第46行结束。也就是说如果用户关闭窗口,则canThreadFinish标志字段会被设置为true(第94行),线程中的循环运行条件不再满足(第46行)从而导致线程方法退出,但线程必须运行46行才能检测canThreadFinish字段的值,也就是说在线程方法退出时,编程人员可以确切的知道哪些代码被执行了,哪些代码不再执行;

编程人员也可以合理的安排线程退出前要做什么事情,例如将线程入口方法改为如下代码:

    public void ThreadWork(object arg) {
        int num = 1;
        while (true) {
            if (this.canThreadFinish == true) {
                MessageBox.Show("注意,我要退出啦!");
                // 其它处理线程退出前工作的代码 
                break;
            }
            this.Invoke(new SetTextBoxHandler(this.SetTextBox), arg, num++);
            Thread.Sleep(10);
        }
    }

这就叫做优雅的退出线程。

对于在线程中使用Control类的Invoke方法通过委访问行窗体或控件方法或属性的情况,还有几点注意事项:

窗体不能先于线程销毁,因为线程总是在特定语句(例如第46行)退出,所以改变标志变量(canThreadFinish)后,线程并不是立即退出,有可能还会运行一些代码后才能执行到特定语句退出线程。当线程代码运行到Invoke方法时,如果窗体已经被关闭,则会引发异常。此时,窗体必须等待并确保线程确实退出了才能关闭;
不能在创建窗体的线程(一般为主线程)中等待辅助线程结束(调用线程对象的Join方法),因为调用了Join方法的线程会被阻塞(即不再向下运行,而在Join方法上等待),而Invoke方法会向窗体所在线程的消息循环发送执行委托的消息,窗体线程被阻塞后消息循环也会被阻塞,Invoke方法自然也会被阻塞,结果就是窗体线程和辅助线程均被阻塞,程序进入死锁;
解决上述问题的方法很简单:在窗体关闭事件中取消窗体关闭(第101行)并启动一个辅助线程(第98行)来等候其它辅助线程结束(第64-80行),并在等候成功后由该线程通过委托方法(第85-87行)来关闭窗体(第79行)。用辅助线程来等待其它辅助线程结束,则不会阻塞窗体线程,等待成功后关闭窗体,则可以保证窗体在这些线程退出后才安全关闭。
本节代码下载

3、前台线程和后台线程

如果不做特殊说明,Thread类默认为前台线程。

所谓前台线程,即该线程会阻止进程结束。即便主线程结束了,只要进程中还有一个前台线程尚未结束,则进程不会结束,直到所有的前台线程都结束。这是最安全的一种方式,保证线程在结束前做完其所有工作。

如果一个线程的工作不涉及I/O操作(例如进行数据计算,异步更新视图等),则可以设置其为后台线程,这样的线程无需关心其如何退出,只要主线程退出,这些线程就会被强制结束。

设置线程对象的IsBackground属性可以将其设置为后台线程或从后台线程设置为前台线程,例如:

    Thread thread = new Thread(/*某入口方法委托*/);
    // 设置线程为后台线程
    thread.IsBackground = true;
    thread.Start(/*入口方法参数*/);

将IsBackground属性设置为false,此时只要主线程退出,这个辅助线程就会自行终止。

将IsBackground属性设置为true,线程就又会成为前台线程。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/mousebaby808/archive/2010/04/11/5471699.aspx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值