第十章 多线程开发

目录

1 线程的创建、启动和停止

1.1 创建线程对象

1.2 线程的启动、暂停和提前中止

1.3 背景线程

1.4 等待一个线程的完成

2 Windows 操作系统线程调度策略

2.1 线程状态

2.2 线程优先级

2.3 Windows 操作系统的抢先式线程调度策略

3 向线程函数传送信息的方式

3.1 添加 “外壳” 方法

3.2 使用带参数的 ParameterizedThreadStart 委托

3.3 设计线程输入参数类


多线程开发技术基础

.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 属性了解线程对象所处的当前状态,如下图所示。

Aborted

 

线程状态包括 AbortRequested 并且该线程现在已死,但其状态尚未更改为 Stopped

AbortRequested

 

已对线程调用了 Abort(Object) 方法,但线程尚未收到试图终止它的挂起的 ThreadAbortException

Background

 

线程正作为后台线程执行(相对于前台线程而言)。 此状态可以通过设置 IsBackground 属性来控制。

Running

 

线程已启动且尚未停止。

Stopped

 

线程已停止。

StopRequested

 

正在请求线程停止。 这仅用于内部。

Suspended

 

线程已挂起。

SuspendRequested

 

正在请求线程挂起。

Unstarted

 

尚未对线程调用 Start() 方法。

WaitSleepJoin

 

线程已被阻止。 这可能是调用 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;
}

 


 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值