线程操作主要包括线程的创建和管理,以及如何将数据传递到托管线程并返回线程代码的执行结果。
19.2.1 创建线程
创建线程是通过创建新的Thread对象来实现的。Thread类的构造函数需要一个ThreadStart委托或ParameterizedThreadStart委托作为参数。
ThreadStart委托和ParameterizedThreadStart委托表示在Thread上执行的方法。它们的定义如下:
public delegate void ThreadStart ();
public delegate void ParameterizedThreadStart (Object obj);
其中的参数obj包含该线程过程的数据对象。
在调用Thread对象的Start()方法之前,该线程不会开始执行。Start方法立即返回,经常是在实际启动新线程之前返回。新的线程将从ThreadStart或ParameterizedThreadStart委托表示的方法的第一行开始执行。只能调用一次Start()方法,多次调用将引发ThreadStateException异常。
线程启动后可以使用ThreadState和IsAlive属性来确定任何时刻的线程状态,但是绝不应该将这些属性用于同步线程的活动。
下面的程序创建两个新线程,分别执行另一个类的实例和静态方法。
// Threads.cs
// 线程示例
using System;
using System.Threading;
public class ServerClass
{
// 线程执行的实例方法
public void InstanceMethod()
{
Console.WriteLine("\t在另一个线程中开始执行InstanceMethod");
// 做些操作
Thread.Sleep(1000);
for (int i = 0; i < 8; ++i)
{
Thread.Sleep(50);
Console.WriteLine("\t\t<{0}>执行InstanceMethod操作++++++", i+1);
}
Console.WriteLine("\t结束InstanceMethod");
}
// 线程执行的静态方法
public static void StaticMethod()
{
Console.WriteLine("\t在另一个线程中开始执行StaticMethod");
// 做些操作
Thread.Sleep(1000);
for (int i = 0; i < 8; ++i)
{
Thread.Sleep(50);
Console.WriteLine("\t\t<{0}>--------执行StaticMethod操作", i+1);
}
Console.WriteLine("\t结束StaticMethod");
}
}
public class Test
{
public static void Main()
{
ServerClass serverObject = new ServerClass();
// 创建使用实例方法的线程
Thread InstanceCaller = new Thread(
new ThreadStart(serverObject.InstanceMethod));
// 创建使用静态方法的线程
Thread StaticCaller = new Thread(
new ThreadStart(ServerClass.StaticMethod));
Console.WriteLine("启动使用InstanceMethod的线程");
InstanceCaller.Start();
Console.WriteLine("启动使用StaticMethod的线程");
StaticCaller.Start();
}
}
从下面的输出结果可以看出,两个线程是并行执行的:
启动使用InstanceMethod的线程
在另一个线程中开始执行InstanceMethod
启动使用StaticMethod的线程
在另一个线程中开始执行StaticMethod
<1>执行InstanceMethod操作++++++
<1>--------执行StaticMethod操作
<2>执行InstanceMethod操作++++++
<2>--------执行StaticMethod操作
<3>执行InstanceMethod操作++++++
<3>--------执行StaticMethod操作
<4>执行InstanceMethod操作++++++
<4>--------执行StaticMethod操作
<5>执行InstanceMethod操作++++++
<5>--------执行StaticMethod操作
<6>执行InstanceMethod操作++++++
<6>--------执行StaticMethod操作
<7>执行InstanceMethod操作++++++
<7>--------执行StaticMethod操作
<8>执行InstanceMethod操作++++++
结束InstanceMethod
<8>--------执行StaticMethod操作
结束StaticMethod
如果在创建线程时使用ParameterizedThreadStart委托,就能在启动线程时将包含数据的对象传递给Thread.Start(Object)方法,并从线程启动的方法中接受该数据,这是一种向线程传递数据的简单方法。请看下面的程序:
// ThreadWithParameter.cs
// 向线程传递参数的示例
using System;
using System.Threading;
public class ServerClass
{
// 线程执行的实例方法
public void InstanceMethod(object times)
{
Console.WriteLine("\t在另一个线程中开始执行InstanceMethod");
// 做些操作
Thread.Sleep(1000);
for (int i = 0; i < (int)times; ++i)
{
Thread.Sleep(50);
Console.WriteLine("\t\t<{0}>执行InstanceMethod操作++++++",
i + 1);
}
Console.WriteLine("\t结束InstanceMethod");
}
// 线程执行的静态方法
public static void StaticMethod(object info)
{
Console.WriteLine("\t在另一个线程中开始执行StaticMethod");
// 做些操作
Thread.Sleep(1000);
for (int i = 0; i < 8; ++i)
{
Thread.Sleep(50);
Console.WriteLine("\t\t<{0}>--------执行StaticMethod操作, {1}",
i + 1, info);
}
Console.WriteLine("\t结束StaticMethod");
}
}
public class Test
{
public static void Main()
{
ServerClass serverObject = new ServerClass();
// 创建使用实例方法的线程
Thread InstanceCaller = new Thread(
new ParameterizedThreadStart(serverObject.InstanceMethod));
// 创建使用静态方法的线程
Thread StaticCaller = new Thread(
new ParameterizedThreadStart(ServerClass.StaticMethod));
Console.WriteLine("启动使用InstanceMethod的线程");
InstanceCaller.Start(6);
Console.WriteLine("启动使用StaticMethod的线程");
StaticCaller.Start("本线程由Test.Main()启动");
}
}
在上面的程序中,向第一个线程传递了循环次数,向第二个线程传递了一个字符串,其输出结果如下:
启动使用InstanceMethod的线程
在另一个线程中开始执行InstanceMethod
启动使用StaticMethod的线程
在另一个线程中开始执行StaticMethod
<1>执行InstanceMethod操作++++++
<1>--------执行StaticMethod操作, 本线程由Test.Main()启动
<2>执行InstanceMethod操作++++++
<2>--------执行StaticMethod操作, 本线程由Test.Main()启动
<3>执行InstanceMethod操作++++++
<3>--------执行StaticMethod操作, 本线程由Test.Main()启动
<4>执行InstanceMethod操作++++++
<4>--------执行StaticMethod操作, 本线程由Test.Main()启动
<5>执行InstanceMethod操作++++++
<5>--------执行StaticMethod操作, 本线程由Test.Main()启动
<6>执行InstanceMethod操作++++++
结束InstanceMethod
<6>--------执行StaticMethod操作, 本线程由Test.Main()启动
<7>--------执行StaticMethod操作, 本线程由Test.Main()启动
<8>--------执行StaticMethod操作, 本线程由Test.Main()启动
结束StaticMethod
使用ParameterizedThreadStart委托来向线程传递参数时,由于可以将任何对象传递给Thread.Start(Object),因此这种方法并不是类型安全的。将数据传递给线程过程的一个更可靠的方法是将线程过程和数据字段都封装在一个辅助类中,并使用ThreadStart委托来执行线程过程。下面的示例说明了如何实现这种方法。
// ThreadWithParameter2.cs
// 向线程传递参数的示例
using System;
using System.Threading;
// DataForThread类封装了线程所需的数据和线程执行的方法
public class DataForThread
{
// 执行线程所需的数据
private string text;
private int number;
// 通过构造函数来获取数据
public DataForThread(string text, int number)
{
this.text = text;
this.number = number;
}
// 线程执行的方法
public void ThreadProc()
{
Console.WriteLine("\t在线程中开始执行方法");
// 做些操作
for (int i = 0; i < number; ++i)
{
Console.WriteLine("\t\t<{0}> 执行线程操作, {1}", i + 1, text);
}
Console.WriteLine("\t结束线程方法");
}
}
public class Test
{
public static void Main()
{
// 构造线程数据
DataForThread dft = new DataForThread("本线程由Test.Main()启动", 10);
// 创建线程
Thread t = new Thread(new ThreadStart(dft.ThreadProc));
Console.WriteLine("启动线程");
t.Start();
}
}
其输出结果如下:
启动线程
在线程中开始执行方法
<1> 执行线程操作, 本线程由Test.Main()启动
<2> 执行线程操作, 本线程由Test.Main()启动
<3> 执行线程操作, 本线程由Test.Main()启动
<4> 执行线程操作, 本线程由Test.Main()启动
<5> 执行线程操作, 本线程由Test.Main()启动
<6> 执行线程操作, 本线程由Test.Main()启动
<7> 执行线程操作, 本线程由Test.Main()启动
<8> 执行线程操作, 本线程由Test.Main()启动
<9> 执行线程操作, 本线程由Test.Main()启动
<10> 执行线程操作, 本线程由Test.Main()启动
结束线程方法
由于ThreadStart和ParameterizedThreadStart这两个委托都没有返回值,因此不能直接从异步调用中返回数据。为了检索线程方法的执行结果,可以使用回调方法。下面的示例演示了一个从线程中检索数据的回调方法。包含数据和线程方法的类的构造函数也接受代表回调方法的委托。在线程方法结束前,它调用该回调委托,以得到返回的数据。
// GetDataBackFromThread.cs
// 从线程中检索数据的示例
using System;
using System.Threading;
// 定义回调方法的委托
public delegate void GetDataCallback(int lineCount);
// DataForThread类封装了线程所需的数据和线程执行的方法
public class DataForThread
{
// 执行线程所需的数据
private string text;
private int number;
// 在线程结束时,返回数据的委托
private GetDataCallback callback;
// 通过构造函数来获取数据
public DataForThread(string text, int number, GetDataCallback callback)
{
this.text = text;
this.number = number;
this.callback = callback;
}
// 线程执行的方法
public void ThreadProc()
{
int lineCount = 0;
Console.WriteLine("\t在线程中开始执行方法");
lineCount++;
// 做些操作
for (int i = 0; i < number; ++i)
{
Console.WriteLine("\t\t<{0}> 执行线程操作, {1}", i + 1, text);
lineCount++;
}
Console.WriteLine("\t结束线程方法");
lineCount++;
if (callback != null)
callback(lineCount);
}
}
public class Test
{
public static void Main()
{
// 构造线程数据
DataForThread dft = new DataForThread("本线程由Test.Main()启动", 10,
new GetDataCallback(ResultCallback));
// 创建线程
Thread t = new Thread(new ThreadStart(dft.ThreadProc));
Console.WriteLine("启动线程");
t.Start();
}
// 回调方法,必须与回调委托的签名一致
public static void ResultCallback(int lineCount)
{
Console.WriteLine("线程打印了{0}行文字", lineCount);
}
}
该程序的输出结果如下:
启动线程
在线程中开始执行方法
<1> 执行线程操作, 本线程由Test.Main()启动
<2> 执行线程操作, 本线程由Test.Main()启动
<3> 执行线程操作, 本线程由Test.Main()启动
<4> 执行线程操作, 本线程由Test.Main()启动
<5> 执行线程操作, 本线程由Test.Main()启动
<6> 执行线程操作, 本线程由Test.Main()启动
<7> 执行线程操作, 本线程由Test.Main()启动
<8> 执行线程操作, 本线程由Test.Main()启动
<9> 执行线程操作, 本线程由Test.Main()启动
<10> 执行线程操作, 本线程由Test.Main()启动
结束线程方法
线程打印了12行文字
匿名方法可以简化与委托相关的操作。在创建线程时需要传入一个指定线程执行方法的委托实例,可以使用匿名方法来实现这个委托实例。匿名方法非常适合于线程方法较简单的情形,可以避免单独编写一个线程方法,以简化代码。
下面的示例说明了如何用匿名方法来创建线程:
// AnonymousMethodAndThread.cs
// 用匿名方法来创建线程的示例
using System;
using System.Threading;
public class Test
{
// Main启动主线程,称之为线程1
public static void Main()
{
Console.WriteLine("进入主线程");
Console.WriteLine("启动子线程1");
Thread thread1 = new Thread(delegate()
{
Console.WriteLine("进入子线程1");
for (int i = 1; i < 4; ++i)
{
Thread.Sleep(50);
Console.WriteLine("\t+++++++子线程1+++++++++");
}
Console.WriteLine("退出子线程1");
});
thread1.Start();
Console.WriteLine("启动线程2");
Thread thread2 = new Thread(delegate()
{
Console.WriteLine("进入子线程2");
for (int i = 1; i < 8; ++i)
{
Thread.Sleep(50);
Console.WriteLine("\t+++++++子线程2+++++++++");
}
Console.WriteLine("退出子线程2");
});
thread2.Start();
Console.WriteLine("Join子线程1");
thread1.Join();
Console.WriteLine("Join子线程2");
thread2.Join();
}
19.2.2 线程优先级
线程优先级指定一个线程相对于另一个线程的相对优先级。System.Threading命名空间中的ThreadPriority枚举定义了一组线程优先级的所有可能值,如表19-2所示。
系统会为每个线程分配一个优先级。对于在公共语言运行时内创建的线程,默认的优先级为Normal,而在运行库外创建的线程在进入运行库时将保留其先前的优先级。可以通过访问Thread对象的Priority属性来获取和设置其优先级。
表19-2 ThreadPriority定义的枚举常数
成员名称 |
说 明 |
AboveNormal |
可以将Thread安排在具有Highest优先级的线程之后,具有Normal优先级的线程之前 |
BelowNormal |
可以将Thread安排在具有Normal优先级的线程之后,具有Lowest优先级的线程之前 |
Highest |
可以将Thread安排在具有任何其他优先级的线程之前 |
Lowest |
可以将Thread安排在具有任何其他优先级的线程之后 |
Normal |
可以将Thread安排在具有AboveNormal优先级的线程之后,在具有BelowNormal优先级的线程之前。默认情况下,线程具有Normal优先级 |
线程是根据其优先级来调度执行的。即使线程运行于公共语言运行时中,线程的处理器时间片依然由操作系统类分配。线程的执行顺序由调度算法来确定。不同的操作系统可能使用不同的调度算法。在某些操作系统中,具有最高优先级(相对于可执行线程而言)的线程经过调度后总是首先运行。如果具有相同优先级的多个线程都可以运行,那么调度程序将遍历处于该优先级的所有线程,并为每个线程提供一个固定的时间片。只要具有较高优先级的线程可以运行,具有较低优先级的线程就不会执行。如果在给定的优先级上不再有可运行的线程,则调度程序将移到下一个较低的优先级,并在该优先级上调度线程。如果具有较高优先级的线程可以运行,则具有较低优先级的线程将被抢先,并允许具有较高优先级的线程再次执行。此外,当应用程序的用户界面在前台和后台之间切换时,操作系统还可以动态地调整线程的优先级。
一个线程的优先级不会影响该线程的状态。只有状态为Running的线程才能被操作系统调度。
下面的代码说明了更改线程优先级的结果。创建5个线程,分别将它们的优先级值设为5种不同的级别,同时启动它们。
// ThreadPriority.cs
// 线程优先级示例
using System;
using System.Threading;
class Test
{
static void Main()
{
ThreadStart startDelegate = new ThreadStart(ThreadProc);
Console.WriteLine("启动五个具有不同优先级的线程");
Thread thread1 = new Thread(startDelegate);
thread1.Name = "线程一";
Thread thread2 = new Thread(startDelegate);
thread2.Name = "线程二";
Thread thread3 = new Thread(startDelegate);
thread3.Name = "线程三";
Thread thread4 = new Thread(startDelegate);
thread4.Name = "线程四";
Thread thread5 = new Thread(startDelegate);
thread5.Name = "线程五";
thread1.Priority = ThreadPriority.Highest;
thread2.Priority = ThreadPriority.AboveNormal;
thread3.Priority = ThreadPriority.Normal;
thread4.Priority = ThreadPriority.BelowNormal;
thread5.Priority = ThreadPriority.Lowest;
thread5.Start();
thread4.Start();
thread3.Start();
thread2.Start();
thread1.Start();
}
public static void ThreadProc()
{
// 做些操作
Thread.Sleep(1000);
for (int i = 0; i < 4; ++i)
{
Thread.Sleep(50);
Console.WriteLine("\t<{0}> {1} - {2}", i + 1,
Thread.CurrentThread.Name, Thread.CurrentThread.Priority);
}
}
}
上述程序的运行结果如下所示。可以可以看出,优先级高的线程被优先执行。
启动五个具有不同优先级的线程
<1> 线程一 - Highest
<1> 线程二 - AboveNormal
<1> 线程三 - Normal
<1> 线程四 - BelowNormal
<1> 线程五 - Lowest
<2> 线程一 - Highest
<2> 线程二 - AboveNormal
<2> 线程三 - Normal
<2> 线程四 - BelowNormal
<2> 线程五 - Lowest
<3> 线程一 - Highest
<3> 线程二 - AboveNormal
<3> 线程三 - Normal
<3> 线程四 - BelowNormal
<3> 线程五 - Lowest
<4> 线程一 - Highest
<4> 线程二 - AboveNormal
<4> 线程三 - Normal
<4> 线程四 - BelowNormal
<4> 线程五 - Lowest
19.2.3 阻塞和中断线程
在一个线程中调用Thread.Sleep方法会导致该线程立即被阻塞,阻塞的时间长度等于传递给Thread.Sleep方法的数值(单位为毫秒)。
如果调用Thread.Sleep方法时传入的参数为Timeout.Infinit,那么当前线程将永远休眠,直到被中断或者终止为止。
在另一个线程中对被阻塞的线程调用Thread对象的Interrupt方法,就会在被阻塞的线程中引发ThreadInterruptedException异常,将中断被阻塞的线程,从而使该线程摆脱阻塞。应该在被阻塞的线程中捕获ThreadInterruptedException异常,并执行适当的操作以继续运行线程。如果线程忽略该异常,则运行库将捕获该异常并停止该线程。
下面的程序演示了阻塞和中断线程的方法:
// SleepAndInterrupt.cs
// 线程的阻塞和中断示例
using System;
using System.Threading;
class Test
{
static void Main()
{
// 创建线程
Thread thread = new Thread(new ThreadStart(ThreadProc));
Console.WriteLine("启动线程");
// 启动线程
thread.Start();
Console.WriteLine("按Enter键中断线程...");
Console.Read();
// 中断线程
thread.Interrupt();
// 等待直到线程thread结束
thread.Join();
}
public static void ThreadProc()
{
Console.WriteLine("线程休眠");
try
{
// 线程休眠
Thread.Sleep(Timeout.Infinite);
}
catch (ThreadInterruptedException ex)
{
Console.WriteLine("线程中断:" + ex.Message);
// 做些操作
for (int i = 0; i < 4; ++i)
{
Console.WriteLine("<{0}> 执行线程", i + 1);
}
}
Console.WriteLine("线程结束");
}
}
上面的程序调用Thread对象的Join方法来等待该线程结束。调用Join方法时,还可以指定一个说明超时间隔的参数,在线程实际停止执行或者指定的超时间隔结束之前,该方法都不会返回。
上述程序中,线程显示“线程休眠”后就被阻塞,直到用户按一个键,才会被调用Interrupt方法来中断。其输出结果如下:
启动线程
线程休眠
按Enter键中断线程...
线程中断:Thread was interrupted from a waiting state.
<1> 执行线程
<2> 执行线程
<3> 执行线程
<4> 执行线程
线程结束
注意 在.NET框架2.0中,System.Threading.Thread.Suspend和System.Threading. Thread.Resume方法已标记为过时,并将从未来版本中移除。应该尽量避免使用它们。
19.2.4 终止线程
Thread类的Abort方法用于永久地停止托管线程。调用Abort时,公共语言运行时在目标线程中引发ThreadAbortException,目标线程可捕捉此异常。线程一旦被终止,它将无法重新启动。
Abort方法不直接导致线程终止,因为目标线程可捕捉ThreadAbortException,并在finally块中执行任意数量的代码。线程的清理代码必须在catch子句或finally子句中,因为系统在finally子句的结尾处再次引发ThreadAbortException异常,如果没有finally子句,则在catch子句的结尾处再次引发该异常。通过调用Thread类的ResetAbort方法,可以防止系统再次引发该异常。但是,只有在你自己的代码中引发ThreadAbortException异常时,才应当这样做。
下面的程序说明了终止线程的方法。
// Abort.cs
// 终止线程示例
using System;
using System.Threading;
class Test
{
static void Main()
{
Thread thread = new Thread(new ThreadStart(ThreadProc));
Console.WriteLine("启动线程");
// 启动线程
thread.Start();
Console.WriteLine("按Enter键终止线程...");
Console.Read();
// 中断线程
thread.Abort();
// 直到线程结束
thread.Join();
}
public static void ThreadProc()
{
Console.WriteLine("线程开始执行");
try
{
// 线程操作
for (long i = 0; i < 400000; ++i)
{
Thread.Sleep(50);
Console.Write("{0}", i + 1);
}
}
catch (ThreadAbortException ex)
{
Console.WriteLine("线程收到终止请求:" + ex.Message);
// 清理操作
for (int i = 0; i < 4; ++i)
{
Console.WriteLine("<{0}>清理一", i + 1);
}
}
finally
{
// 清理操作
for (int i = 0; i < 4; ++i)
{
Console.WriteLine("<{0}>清理二", i + 1);
}
}
Console.WriteLine("线程结束");
}
}
上述程序的输出结果如下:
启动线程
线程开始执行
按Enter键终止线程...
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
线程收到终止请求:Thread was being aborted.
<1>清理一
<2>清理一
<3>清理一
<4>清理一
<1>清理二
<2>清理二
<3>清理二
<4>清理二
可以看出,线程被终止时并不会立即退出,用户还可以在try语句的catch和finally块中设置自己的清理代码。线程被终止后,将继续执行这些代码。但是,任何位于catch和finally块之后的语句都不会被执行。在上例中,线程方法的最后一个语句(Console.WriteLine("线程结束");)就未被执行。
19.2.5 线程状态
System.Threading.ThreadState枚举指定Thread的执行状态,此枚举有一个FlagsAttribute属性,允许其成员值按位组合。ThreadState定义的枚举常数如表19-3所示。
表19-3 ThreadState定义的枚举常数
成员名称 |
说 明 |
Aborted |
线程处于Stopped状态中 |
AbortRequested |
已对线程调用了Thread.Abort方法,但线程尚未收到试图终止它的挂起的System.Threading. ThreadAbortException |
Background |
线程正作为后台线程执行(相对于前台线程而言)。此状态可以通过设置Thread.IsBackground属性来控制 |
Running |
线程已启动,它未被阻塞,并且没有挂起的ThreadAbortException |
Stopped |
线程已停止 |
StopRequested |
正在请求线程停止。这仅用于内部 |
Suspended |
线程已挂起 |
SuspendRequested |
正在请求线程挂起 |
Unstarted |
尚未对线程调用Thread.Start方法 |
WaitSleepJoin |
由于调用Wait、Sleep或Join,线程已被阻止 |
Thread对象的ThreadState属性提供一个由ThreadState定义的位掩码,它指示线程的当前状态。一个线程至少总是处于ThreadState枚举中定义的一个可能状态,并且可以同时处于多个状态。
注意,只能在一些调试方案中使用线程状态,而不应该在代码中使用线程状态来同步线程活动。
在创建托管线程时,该线程处于Unstarted状态。线程会保持Unstarted状态,直到被操作系统调度到已启动状态。调用Start方法使操作系统知道该线程可启动,但是它并不直接更改线程的状态。一旦线程处于已启动的状态中,就可以执行许多操作来使线程更改状态。表19-4列出了使状态发生更改的操作,以及相应的新状态。
表19-4 使线程状态发生更改的操作及相应的新状态
操 作 |
ThreadState |
在公共语言运行库中创建线程 |
Unstarted |
线程调用Start |
Unstarted |
线程开始运行 |
Running |
续表
操 作 |
ThreadState |
线程调用Sleep |
WaitSleepJoin |
线程对其他对象调用Wait |
WaitSleepJoin |
线程对其他线程调用Join |
WaitSleepJoin |
另一个线程调用Interrupt |
Running |
另一个线程调用Suspend |
SuspendRequested |
线程响应Suspend请求 |
Suspended |
另一个线程调用Resume |
Running |
另一个线程调用Abort |
AbortRequested |
线程响应Abort请求 |
Stopped |
线程被终止 |
Stopped |
由于Running状态的值为0,因此不可能执行位测试来发现此状态。应该使用如下的测试方法:
if ((state & (ThreadState.Unstarted | ThreadState.Stopped)) == 0)
{
// 处于Running状态
}
在任何时间内,线程通常都处于多个状态中。例如,如果当某个线程因调用Monitor.Wait方法而被阻止时,另一个线程调用该线程的Abort方法,那么该线程将同时处于WaitSleepJoin和AbortRequested状态。在这种情况下,一旦该线程从对Wait方法的调用中返回或被中断,它就会收到ThreadAbortException异常。
一旦线程因调用Start方法而离开Unstarted状态,它就无法再返回到Unstarted状态。同样,线程也永远无法从Stopped状态转移到其他状态。
Thread类还有两个属性值与线程的状态相关,即IsAlive和IsBackground。IsAlive是只读属性,说明线程是否已经启动并且没有结束。IsBackground用于了解线程是否是后台线程,以及将线程设置为后台或者前台线程。
如果IsBackground为true,则说明线程是后台线程,Thread对象的ThreadState值包含Background。否则,线程为前台台进程,Thread对象的ThreadState值不包含Background。托管线程不是后台线程,就是前台线程。后台线程不会使托管执行环境处于运行状态,除此之外,后台线程与前台线程是一样的。一旦所有前台线程在托管进程中被停止,系统将停止所有后台线程并关闭。直到所有前台线程都终止了,应用程序才会结束。当应用程序结束时,所有的后台线程也会自动终止。
通过将IsBackground设置为true,可以将线程设置为后台线程;通过将IsBackground设置为false,可以将线程设置为前台线程。
下面的程序很清晰地说明了线程的状态变化:
// States.cs
// 线程状态示例
using System;
using System.Threading;
class Test
{
static void Main()
{
Console.WriteLine("创建线程");
// 创建线程
Thread thread = new Thread(new ThreadStart(ThreadProc));
DisplayThreadState(thread);
Console.WriteLine("启动线程");
// 启动线程
thread.Start();
// 主线程休眠一秒,等待子线程进入休眠
Thread.Sleep(1000);
DisplayThreadState(thread);
Console.WriteLine("将线程设置为后台线程");
// 将线程设置为后台线程
thread.IsBackground = true;
DisplayThreadState(thread);
Console.WriteLine("将线程设置为前台线程");
// 将线程设置为前台线程
thread.IsBackground = false;
DisplayThreadState(thread);
Console.WriteLine("按Enter键中断线程...");
Console.Read();
// 中断线程
thread.Interrupt();
// 主线程休眠一秒,等待子线程中断
Thread.Sleep(1000);
Console.WriteLine("按X键并按回车键终止线程...");
char ch;
do
{
ch = (char)Console.Read();
} while (ch != 'X' && ch != 'x');
// 终止线程
thread.Abort();
// 直到线程结束
thread.Join();
DisplayThreadState(thread);
}
public static void DisplayThreadState(Thread t)
{
Console.WriteLine("\t\t-----------------------------------");
if (t.IsAlive)
{
Console.WriteLine(t.IsBackground ? "\t\t|\t后台线程" :
"\t\t|\t前台线程");
if ((t.ThreadState & ThreadState.AbortRequested) ==
ThreadState.AbortRequested)
Console.WriteLine("\t\t|\t已要求终止线程");
if ((t.ThreadState &
(ThreadState.Stopped | ThreadState.Unstarted)) == 0)
Console.WriteLine("\t\t|\t线程正在运行");
if ((t.ThreadState & ThreadState.StopRequested) ==
ThreadState.StopRequested)
Console.WriteLine("\t\t|\t已要求停止线程");
if ((t.ThreadState & ThreadState.WaitSleepJoin) ==
ThreadState.WaitSleepJoin)
Console.WriteLine("\t\t|\t线程被阻塞");
}
else
{
if ((t.ThreadState & ThreadState.Unstarted) ==
ThreadState.Unstarted)
Console.WriteLine("\t\t|\t线程未启动");
if ((t.ThreadState & ThreadState.Stopped) == ThreadState.Stopped)
Console.WriteLine("\t\t|\t线程已经停止");
}
Console.WriteLine("\t\t-----------------------------------");
}
public static void ThreadProc()
{
Console.WriteLine("线程开始执行");
DisplayThreadState(Thread.CurrentThread);
Console.WriteLine("线程休眠");
try
{
// 线程休眠
Thread.Sleep(Timeout.Infinite);
}
catch (ThreadInterruptedException ex)
{
Console.WriteLine("线程中断:" + ex.Message);
DisplayThreadState(Thread.CurrentThread);
}
// 子线程休眠一秒,等待主线程显示信息
Thread.Sleep(1000);
try
{
// 线程操作
for (long i = 0; i < 400000; ++i)
{
Thread.Sleep(50);
Console.Write("{0}", i + 1);
}
}
catch (ThreadAbortException ex)
{
Console.WriteLine("线程收到终止请求:" + ex.Message);
DisplayThreadState(Thread.CurrentThread);
}
Console.WriteLine("线程结束");
}
}
下面列出了上述程序的一种运行结果:
创建线程
-----------------------------------
| 线程未启动
-----------------------------------
启动线程
线程开始执行
-----------------------------------
| 前台线程
| 线程正在运行
-----------------------------------
线程休眠
-----------------------------------
| 前台线程
| 线程正在运行
| 线程被阻塞
-----------------------------------
将线程设置为后台线程
-----------------------------------
| 后台线程
| 线程正在运行
| 线程被阻塞
-----------------------------------
将线程设置为前台线程
-----------------------------------
| 前台线程
| 线程正在运行
| 线程被阻塞
-----------------------------------
按Enter键中断线程...
线程中断:Thread was interrupted from a waiting state.
-----------------------------------
| 前台线程
| 线程正在运行
-----------------------------------
按X键并按回车键终止线程...
12345678910111213x1415161718192021
线程收到终止请求:Thread was being aborted.
-----------------------------------
| 前台线程
| 已要求终止线程
| 线程正在运行
-----------------------------------
-----------------------------------
| 线程已经停止
-----------------------------------
左边列出的是对线程的操作,右边虚框中列出了线程的状态。
|
编译生成上述程序的可执行程序。运行它,体会线程状态的变化。