目录
3.2 使用带参数的 ParameterizedThreadStart 委托
多线程开发技术基础
.NET 基类库提供了一个 Thread 类,它的实例代表一个托管线程。
1 线程的创建、启动和停止
1.1 创建线程对象
Thread 类拥有 4 个重载的构造函数,最常用的两个如下:
public Thread(ThreadStart start);
public Thread(ParameterizedThreadStart start);
ThreadStart 与 ParameterizedThreadStart 是一个委托,其定义如下:
public delegate void ThreadStart();
public delegate void ParameterizedThreadStart(object obj);
从以上定义可知,在创建线程对象时必须传给它一个方法,这个方法被称为 “线程方法”。
每个线程都对应着一个特定的线程函数,线程的执行体现着线程函数的执行。
Thread m_objThread = new Thread(__PlayThread);
其实是以下代码的简写格式:
Thread m_objThread = new Thread(new ThreadStart(__PlayThread));
1.2 线程的启动、暂停和提前中止
m_objThread.Start(); // 调用 Start 方法启动线程
当线程函数代码执行完毕,线程自然结束。
可以使用 Thread.Sleep 方法让线程对象 “休眠” 一段时间,此方法的定义如下:
public static void Sleep(int millisecondsTimeout);
Sleep 方法的参数是一个整数,代表毫秒(1秒 = 1000 毫秒)。
如果在线程函数代码执行完之前需要提前终止线程,可以使用线程对象的 Abort 方法。这时,线程对象自身会引发一个 ThreadAbortException 异常。
static void Main(string[] args)
{
Console.WriteLine("主线程开始");
Thread m_objThread = new Thread(__PlayThread);
m_objThread.Start();
Console.WriteLine("主线程结束.");
}
static public void __PlayThread()
{
Console.WriteLine("辅助线程开始......");
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
Thread.Sleep(500);
}
Console.WriteLine("辅助线程结束.");
}
上述代码执行输出以下结果:
从结果可知,在主线程结束后,辅助线程仍在运行。CLR 要等到辅助线程运行结束后才会结束整个进程。
现在修改代码,让主线程提前终止辅助线程:
static void Main(string[] args)
{
Console.WriteLine("主线程开始");
Thread m_objThread = new Thread(__PlayThread);
m_objThread.Start();
Thread.Sleep(2000);
Console.WriteLine("主线程调用Abort方法提前中止辅助线程...");
m_objThread.Abort();
Console.WriteLine("主线程结束.");
}
输出结果如下图所示,当辅助线程输出到3时,被强行中断。
在很多情况下,强行中断一个线程可能会造成不好的结果,比如无法保存数据,或者是造成资源泄露。例如辅助线程如果正在进行文件操作,强行中断它有可能会导致文件未正常关闭,从而使操作系统无法将线程打开的文件句柄回收。
为了解决这个问题,必须在线程函数中处理线程被提前中断的情况。
当一个线程被提前中止时,它引发一个 ThreadAbortException 异常,可以捕获此异常以进行中断处理,修改后的 MyThread 类代码如下:
static public void __PlayThread()
{
try
{
Console.WriteLine("辅助线程开始......");
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
Thread.Sleep(500);
}
Console.WriteLine("辅助线程结束.");
}
catch (System.Exception ex)
{
Console.WriteLine("辅助线程被提前中止");
}
finally
{
Console.WriteLine("完成清理辅助线程占用的资源工作");
}
}
经过修改后的程序运行结果:
1.3 背景线程
如果我们在主线程中不主动调用 Abort 方法终止辅助线程,则在主线程结束后 CLR 会继续等待辅助线程执行结束,之后才结束整个应用程序进程。
如何设置在主线程结束时让 CLR 自动地强行结束所有还在运行的辅助线程呢?
在创建辅助线程对象时设置其 Background 属性为 true 即可。
Background 属性为 True 的线程被称为 “背景线程”。
static void Main(string[] args)
{
Console.WriteLine("主线程开始");
Thread m_objThread = new Thread(__PlayThread);
m_objThread.IsBackground = true; // 设置为背景线程
m_objThread.Start();
Thread.Sleep(2000);
Console.WriteLine("主线程结束.");
}
由于将辅助线程设置为背景线程,当主线程一结束时,CLR 会对属于此进程的所有背景线程调用 Abort 方法中止。结果如下所示:
注:除非显示地直接将 IsBackground 设置为 true,默认情况下所有创建的线程其 IsBackground 属性都为 false,这种线程称为 “前台线程”。CLR 会等待所有前台线程结束后才会结束整个进程。
“使用背景线程” 是一种让线程 “干净利落” 地退出的最简单方式,因为不需要编写额外的代码来强制结束进程所启动的所有线程。
1.4 等待一个线程的完成
当我们在实际开发中,主线程启动一个辅助线程后,要等待它运行结束之后才能继续干某些工作。
线程之间额这种协作关系称为 “线程同步”。
Thread 类的 Join 方法可以让一个线程等待另一个线程结束。示例程序如下:
static void Main(string[] args)
{
Console.WriteLine("主线程开始运行");
Thread m_objThread = new Thread(__PlayThread);
m_objThread.Start();
m_objThread.Join(); // 调用阻塞主线程,等待辅助线程执行结束
Console.WriteLine("主线程退出.");
}
static public void __PlayThread()
{
Console.WriteLine("辅助线程正在执行......");
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
Thread.Sleep(500);
}
Console.WriteLine("辅助线程执行结束.");
}
执行结果如下:
当我们注释掉 Join() 那条语句时,重新执行结果如下:
2 Windows 操作系统线程调度策略
2.1 线程状态
在 .NET 中,每个托管线程都拥有一系列的状态,在任何时候,每个线程对象一定处于一个确定的状态之中,可通过 Thread 类的 ThreadState 属性了解线程对象所处的当前状态,如下图所示。
| 线程状态包括 AbortRequested 并且该线程现在已死,但其状态尚未更改为 Stopped。 | |
| 已对线程调用了 Abort(Object) 方法,但线程尚未收到试图终止它的挂起的 ThreadAbortException。 | |
| 线程正作为后台线程执行(相对于前台线程而言)。 此状态可以通过设置 IsBackground 属性来控制。 | |
| 线程已启动且尚未停止。 | |
| 线程已停止。 | |
| 正在请求线程停止。 这仅用于内部。 | |
| 线程已挂起。 | |
| 正在请求线程挂起。 | |
| 尚未对线程调用 Start() 方法。 | |
| 线程已被阻止。 这可能是调用 Sleep(Int32) 或 Join()、请求锁定(例如通过调用 Enter(Object) 或 Wait(Object, Int32, Boolean))或在线程同步对象上(例如 ManualResetEvent)等待的结果。 |
2.2 线程优先级
每个线程还关联着一个线程优先级,由 ThreadPriority 属性标识。
.NET Framework 提供了五种线程优先级,从高到低依次为:
Highest → AboveNormal → Normal → BelowNormal → Lowest
默认新创建的线程对象优先级为 Normal
2.3 Windows 操作系统的抢先式线程调度策略
Windows 操作系统处理程序的方法称为 “抢先式线程调度策略”。谁的优先级高,就优先运行。
3 向线程函数传送信息的方式
3.1 添加 “外壳” 方法
如下代码所示,将 SomeFunc 方法的参数和返回值 “外化” 为 MyThread 类的公有字段,然后创建一个符合 ThreadStart 委托要求的 “外套” 方法 __PlayThread,在此方法中调用 SomeFunc 方法。
class Program
{
static void Main(string[] args)
{
MyThread obj = new MyThread { x = 20, y = 30 };
Thread m_objThread = new Thread(obj.__PlayThread);
m_objThread.Start();
m_objThread.Join();
Console.WriteLine(obj.returnValue);
}
}
class MyThread
{
public int x;
public int y;
public long returnValue;
public long SomeFunc(int x, int y)
{
return x + y;
}
public void __PlayThread()
{
returnValue = SomeFunc(x, y);
}
}
3.2 使用带参数的 ParameterizedThreadStart 委托
public Thread(ParameterizedThreadStart start);
public delegate void ParameterizedThreadStart(object obj);
ParameterizedThreadStart 委托可以接收含有一个参数且无返回值的线程函数。由于其参数类型为 object,所以此委托可以接收任何类型的方法参数。
class Program
{
static void Main(string[] args)
{
MyThread obj = new MyThread();
Thread m_objThread = new Thread(new ParameterizedThreadStart(obj.__PlayThread));
m_objThread.Start("Hello World");
}
}
class MyThread
{
public void __PlayThread(object m)
{
Console.WriteLine(m);
}
}
3.3 设计线程输入参数类
自定义一个 Info 类,通过线程函数的参数传递。
class Program
{
static void Main(string[] args)
{
MyThread obj = new MyThread();
Info m_Info = new Info { x = 10, y = 20 };
Thread m_objThread = new Thread(new ParameterizedThreadStart(obj.__PlayThread));
m_objThread.Start(m_Info);
m_objThread.Join();
Console.WriteLine(m_Info.result.ToString());
}
}
class MyThread
{
public void __PlayThread(object m) // 方法必须满足 ParameterizedThreadStart 委托签名
{
int num1, num2;
num1 = (m as Info).x;
num2 = (m as Info).y;
(m as Info).result = num1 + num2;
}
}
class Info
{
public int x;
public int y;
public int result;
}