08-结束线程
21.2.3 结束线程
在C#中,System.Threading命名空间提供了一些可以进行多线程编程的类和接口。除了同步线程活动和数据访问的类之外,此命名空间还包含一个 ThreadPool 类,它使用户能够使用系统提供的线程池和一个 Timer 类,在线程池线程上执行回调方法。Thread类有几个重要的方法,描述如下。
- Abort():调用此方法通常会终止线程。在调用此方法的线程上引发 ThreadAbortException,以开始终止此线程的过程。
- Sleep(int):已重载。 将当前线程阻塞指定的毫秒数。该方法是静态方法。
- Suspend():该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复。
- Resume():恢复被Suspend()方法挂起的线程的执行 。
下面动手编写一个完整线程操作的例子,进行创建线程、运行线程、终止线程的全程操作。
【范例21-6】 C#中的线程程序。
(1)在Visual C#窗口中选择【新建】
【项目】,弹出【新建项目】对话框,在【模板】中选择【控制台应用程序】,在【名称】文本框中输入项目名称“SubThread”。
(2)单击【确定】按钮,直接打开“Program.cs”的代码窗口,然后在“Program.cs”中的program类中输入以下代码(代码21-6-1.txt)。
01 public class SubThread
02 {
03 public void SubThreadFunc() //定义线程方法SubThreadFunc
04 {
05 int i; //定义整型变量i
06 i = 0; //变量i赋初值
07 do //do循环开始
08 {
09 i = i + 1; //i变量增加1
10 Console.WriteLine("子线程正在运行……{0}", i); //输出当前线程状态
11 }
12 while (1 == 1); //特意编写的无限循环
13 }
14 };
**注意** C#不会自动添加对命名空间Threading的引用,用户需要添加“using System.Threading;”完成对线程命名空间的引用。
这段程序定义了一个非常简单的类SubThread,在这个类中仅有一个方法:SubThreadFunc。当这个方法被调用时,只要没有外界的干扰,将会一直输出:“子线程正在运行……”。如果没有线程,我们就没有一个非常好的办法在程序内部中断这种类型的循环,而线程则给我们提供了一种非常完美的方式。
(3)在“Program.cs”中的Main方法中输入以下代码(代码21-6-2.txt)。
01 //输出演示程序的用意
02 Console.WriteLine("子线程启动、停止、终止、阻塞综合演示:");
03 //创建线程ThreadS,演示SubThread类的SubThreadFunc()方法
04 SubThread SThread = new SubThread();
05 Thread ThreadS = new Thread(new ThreadStart(SThread.SubThreadFunc));
06 //输出启动线程
07 Console.WriteLine("子线程启动---------------------;");
08 //启动线程
09 ThreadS.Start();
10 //输出子线程状态
11 Console.WriteLine("子线程状态---------------------{0};", ThreadS.ThreadState);
12 //主线程睡眠1毫秒
13 Thread.Sleep(1);
14 Console.WriteLine("主线程睡眠1毫秒;");
15 //ThreadS终止
16 ThreadS.Abort();
17 //输出线程状态
18 Console.WriteLine();
19 Console.WriteLine("子线程状态---------------------{0};", ThreadS.ThreadState);
20 Console.WriteLine("子线程终止---------------------;");
21 Console.WriteLine("SThread.SubThreadFunc 被终止。");
22 //启动线程,并捕获错误
23 try
24 {
25 ThreadS.Start();
26 Console.WriteLine("尝试启动SThread.SubThreadFunc 线程");
27 }
28 catch (ThreadStateException)
29 {
30 //当出现启动错误时,显示错误提示
31 Console.WriteLine("SThread.SubThreadFunc已经中止线程不能被重新启动。");
32 Console.ReadLine();
33 }
34 return; //返回
【范例分析】
此范例中最关键的代码是步骤(3)中的代码,首先,使用语句“ThreadS.Start();”启动子程序,这个时候,语句“Thread.Sleep(1);”使得主程序休眠1毫秒,给子线程留出运行时间,然后使用语句“ThreadS.Abort();”终止子线程的运行,从而有效地避免了程序无限制地停留在一段无限循环的程序段中。线程为我们提供了一种很好的控制子程序的方法。
在范例中,我们还演示了一个已经被Abort()终止的线程能否被重新启动,在范例运行后的结果中已经看到,一个被终止的线程是不能被重新启动的。在这段程序中为了对我们已经学习到的知识进行扩展,程序中还有意使用了Thread.ThreadState属性,这个属性代表了线程运行时的状态,指示当前线程的状态的枚举值之一,ThreadState 为线程定义了一组所有可能的执行状态。一旦线程被创建,它就至少处于其中的一个状态中,直到终止。在公共语言运行中创建的线程最初处于 Unstarted状态,而进入运行时的外部线程则已经处于 Running 状态。通过调用 Start 方法,可以将 Unstarted 线程转换为 Running 状态。并非所有的 ThreadState 值的组合都是有效的,例如,线程不能同时处于 Aborted 和 Unstarted 状态中。 ThreadState在各种情况下的可能取值如下表所示。
| 成员名称 | 说明 | | :----- | :----- | :----- | :----- | | Running | 线程已启动,它未被阻塞,并且没有挂起的 ThreadAbortException | | StopRequested | 正在请求线程停止。这仅用于内部 | | SuspendRequested | 该线程已被标记为挂起 | | Background | 线程正作为后台线程执行(相对于前台线程而言)。此状态可以通过设置 Thread.IsBackground 属性来控制 | | Unstarted | 尚未对线程调用 Thread.Start 方法 | | Stopped | 线程已停止 | | WaitSleepJoin | 线程已被阻止。这可能是因为调用 Thread.Sleep 或Thread.Join、请求锁定(例如,通过调用Monitor.Enter或 Monitor.Wait)或等待线程同步对象(如 ManualResetEvent) | | Suspended | 线程已挂起 | | AbortRequested | 已对线程调用了 Thread.Abort方法,但线程尚未收到试图终止它的挂起的 System.Threading.ThreadAbortException | | Aborted | 线程状态包括 AbortRequested,并且该线程现在已死,但其状态尚未更改为 Stopped |
【运行结果】
按【Ctrl+F5】组合键不调试直接运行程序,则会弹出控制台窗口,显示类似如下图所示的执行结果。
【拓展训练】
如果子线程想运行更长的时间,我们可以把语句“Thread.Sleep(1);”改为“Thread.Sleep(200);”或者是更大的数值,对相应的注释也可以做相应的修改,这样在运行程序之后,就可以看到子线程明显获得了更长的运行时间。部分代码如下(拓展代码21-6-3.txt)。
01 Console.WriteLine("子线程启动、停止、终止、阻塞综合演示:");
02 SubThread SThread = new SubThread();
03 Thread ThreadS = new Thread(new ThreadStart(SThread.SubThreadFunc));
04 ThreadS.Start();
05 Console.WriteLine("子线程状态---------------------{0};", ThreadS.ThreadState);
06 Thread.Sleep(200); //主线程睡眠200毫秒
**注意**
C#不会自动添加对命名空间Threading的引用,用户需要添加“using System.Threading;”完成对线程命名空间的引用。