在System.Threading 命名空间下,包含了用于创建和控制线程的Thread 类。对线程的常用操作有:启动线程、终止线程、合并线程和让线程休眠等。

1 启动线程

  在使用线程前,首先要创建一个线程。其一般形式为:

Thread t=new Thread(enterPoint);

  其中enterPoint 为线程的入口,即线程开始执行的方法。在托管代码中,通过委托处理线程执行的代码。例如:

Thread t=new Thread(new ThreadStart(methodName));

  创建线程实例后,就可以调用Start 方法启动线程了。

2 终止线程

  线程启动后,当不需要某个线程继续执行的时候,有两种终止线程的方法。
  一种是事先设置一个布尔变量,在其他线程中通过修改该变量的值作为传递给该线程是否需要终止的判断条件,而在该线程中循环判断该条件,以确定是否退出线程,这是结束线程的比较好的方法,实际编程中一般使用这种方法。
  第二种方法是通过调用Thread 类的Abort 方法强行终止线程。例如:

t.Abort();

  Abort 方法没有任何参数,线程一旦被终止,就无法再重新启动。由于Abort 通过抛出异常强行终止结束线程,因此在实际编程中,应该尽量避免采用这种方法。

  调用Abort 方法终止线程时,公共语言运行库(CLR)会引发ThreadAbortException 异常,程序员可以在线程中捕获ThreadAbortException 异常,然后在异常处理的Catch 块或者Finally块中作释放资源等代码处理工作;但是,线程中也可以不捕获ThreadAbortException 异常,而由系统自动进行释放资源等处理工作。
  注意,如果线程中捕获了ThreadAbortException 异常,系统在finally 子句的结尾处会再次引发ThreadAbortException 异常,如果没有finally 子句,则会在Catch 子句的结尾处再次引发该异常。为了避免再次引发异常,可以在finally 子句的结尾处或者Catch 子句的结尾处调用System.Threading.Thread.ResetAbort 方法防止系统再次引发该异常。
  使用Abort 方法终止线程,调用Abort 方法后,线程不一定会立即结束。这是因为系统在结束线程前要进行代码清理等工作,这种机制可以使线程的终止比较安全,但清理代码需要一定的时间,而我们并不知道这个工作将需要多长时间。因此,调用了线程的Abort 方法后,如果系统自动清理代码的工作没有结束,可能会出现类似死机一样的假象。为了解决这个问题,可以在主线程中调用子线程对象的Join 方法,并在Join 方法中指定主线程等待子线程结束的等待时间。

3 合并线程

  Join 方法用于把两个并行执行的线程合并为一个单个的线程。如果一个线程t1 在执行的过程中需要等待另一个线程t2 结束后才能继续执行,可以在t1 的程序模块中调用t2 的join()方法。例如:

t2.Join();

  这样t1 在执行到t2.Join()语句后就会处于阻塞状态,直到t2 结束后才会继续执行。
  但是假如t2 一直不结束,那么等待就没有意义了。为了解决这个问题,可以在调用t2 的Join 方法的时候指定一个等待时间,这样t1 这个线程就不会一直等待下去了。例如,如果希望将t2 合并到t1 后,t1 只等待100 毫秒,然后不论t2 是否结束,t1 都继续执行,就可以在t1中加上语句:

t2.Join(100);

  Join 方法通常和Abort 一起使用。

  由于调用某个线程的Abort 方法后,我们无法确定系统清理代码的工作什么时候才能结束,因此如果希望主线程调用了子线程的Abort 方法后,主线程不必一直等待,可以调用子线程的Join 方法将子线程连接到主线程中,并在连接方法中指定一个最大等待时间,这样就能使主线程继续执行了。

4 让线程休眠

  在多线程应用程序中,有时候并不希望某一个线程继续执行,而是希望该线程暂停一段时间,等待其他线程执行之后再继续执行。这时可以调用Thread 类的Sleep 方法,即让线程休眠。例如:

Thread.Sleep(1000);

  这条语句的功能是让当前线程休眠1000 毫秒。
  注意,调用Sleep 方法的是类本身,而不是类的实例。休眠的是该语句所在的线程,而不是其他线程。

5 线程优先级

  当线程之间争夺CPU 时间片时,CPU 是按照线程的优先级进行服务的。在C#应用程序中,可以对线程设定五个不同的优先级,由高到低分别是Highest、AboveNormal、Normal、BelowNormal 和Lowest。在创建线程时如果不指定其优先级,则系统默认为Normal。假如想让一些重要的线程优先执行,可以使用下面的方法为其赋予较高的优先级:

Thread t=new Thread(new ThreadStart(enterpoint));
t.priority=ThreadPriority.AboveNormal;

  通过设置线程的优先级可以改变线程的执行顺序,所设置的优先级仅仅适用于这些线程所属的进程。
  注意,当把某线程的优先级设置为Highest 时,系统上正在运行的其他线程都会终止,所以使用这个优先级别时要特别小心。

6 线程池

  线程池是一种多线程处理形式,为了提高系统性能,在许多地方都要用到线程池技术。例如,在一个C/S 模式的应用程序中的服务器端,如果每到一个请求就创建一个新线程,然后在新线程中为其请求服务的话,将不可避免的造成系统开销的增大。实际上,创建太多的线程可能会导致由于过度使用系统资源而耗尽内存。为了防止资源不足,服务器端应用程序应采取一定的办法来限制同一时刻处理的线程数目。

  线程池为线程生命周期的开销问题和资源不足问题提供了很好的解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,由于请求到达时线程已经存在,所以无意中也就消除了线程创建所带来的延迟。这样,就可以立即为新线程请求服务,使其应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过了规定的最大数目时,就强制其他任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。

  线程池适用于需要多个线程而实际执行时间又不多的场合,比如有些常处于阻塞状态的线程。当一个应用程序服务器接受大量短小线程的请求时,使用线程池技术是非常合适的,它可以大大减少线程创建和销毁的次数,从而提高服务器的工作效率。但是如果线程要求运行的时间比较长的话,那么此时线程的运行时间比线程的创建时间要长得多,仅靠减少线程的创建时间对系统效率的提高就不是那么明显了,此时就不适合使用线程池技术,而需要借助其他的技术来提高服务器的服务效率。

7 同步

  同步是多线程中一个非常重要的概念。所谓同步,是指多个线程之间存在先后执行顺序的关联关系。如果一个线程必须在另一个线程完成某个工作后才能继续执行,则必须考虑如何让
其保持同步,以确保在系统上同时运行多个线程而不会出现逻辑错误。
  当两个线程t1 和t2 有相同的优先级,并且同时在系统上运行时,如果先把时间片分给t1使用,它在变量variable1 中写入某个值,但如果在时间片用完时它仍没有完成写入,这时由于时间片已经分给t2 使用,而t2 又恰好要尝试读取该变量,它可能就会读出错误的值。这时,如果使用同步仅允许一个线程使用variable1,在该线程完成对variable1 的写入工作后再让t2读取这个值,就可以避免出现此类错误。
  为了对线程中的同步对象进行操作,C#提供了lock 语句锁定需要同步的对象。lock 关键字确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻塞),直到该对象被释放。比如线程t1 对variable1 操作时,为了避免其他线程也对其进行操作,可以使用lock 语句锁定variable1,实现代码为:

lock(variable1)
{
variable1++;
}

  注意,锁定的对象一定要声明为private,不要锁定public 类型的对象,否则将会使lock语句无法控制,从而引发一系列问题。
  另外还要注意,由于锁定一个对象之后,其他任何线程都不能访问这个对象,需要使用该对象的线程就只能等待该对象被解除锁定后才能使用。因此如果在锁定和解锁期间处理的对象过多,就会降低应用程序的性能。
  还有,如果两个不同的线程同时锁定两个不同的变量,而每个线程又都希望在锁定期间访问对方锁定的变量,那么两个线程在得到对方变量的访问权之前都不会释放自己锁定的对象,从而产生死锁。在编写程序时,要注意避免这类操作引起的问题。

  【例】线程的基本用法

  (1) 新建一个名为ThreadExample 的Windows 应用程序,界面设计如图所示。

    (2) 向设计窗体拖放一个Timer 组件,不改变自动生成的对象名。

  (3) 添加命名空间引用:

using System.Threading;

  (4) 在构造函数上方添加字段声明:

StringBuilder sb = new StringBuilder();
Thread thread1;
Thread thread2;

  (5) 直接添加代码:

private void AppendString(string s)
{
lock (sb)
{
sb.Append(s);
}
}
public void Method1()
{
while (true)
{
Thread.Sleep(100); //线程休眠100 毫秒
AppendString("a");
}
}
public void Method2()
{
while (true)
{
Thread.Sleep(100); //线程休眠100 毫秒
AppendString("b");
}
}

  (6) 分别在【启动线程】和【终止线程】按钮的Click 事件中添加代码:

private void buttonStart_Click(object sender, EventArgs e)
{
sb.Remove(0, sb.Length);
timer1.Enabled = true;
thread1 = new Thread(new ThreadStart(Method1));
thread2 = new Thread(new ThreadStart(Method2));
thread1.Start();
thread2.Start();
}
private void buttonAbort_Click(object sender, EventArgs e)
{
thread1.Abort();
thread1.Join(10);
thread2.Abort();
thread2.Join(10);
}

  (7) 在timer1 的Tick 事件中添加代码:

private void timer1_Tick(object sender, EventArgs e)
{
if (thread1.IsAlive == true || thread2.IsAlive == true)
{
richTextBox1.Text = sb.ToString();
}
else
{
timer1.Enabled = false;
}
}

  (8) 按<F5>键编译并执行,单击【启动线程】后,再单击【终止线程】,从运行结果中可以看到,两个具有相同优先级的线程同时执行时,在richTextBox1 中添加的字符个数基本上相同。

C#线程Thread类的更多相关文章

  1. 并发基础(六) 线程Thread类的start()和run()

    start()和run()方法对于刚接触线程的人来说,会有点混淆,有点难理解,一般都会有以下疑问: 一.start( )方法 1.为什么需要start方法:它的作用是什么: start方法的作用就是将 ...

  2. 线程 Thread类 的四个构造方法简介

    在线程中,Thread类有四个构造方法: 当我们使用 Thread类创建对象的时候,传入参数,就会用到构造方法.ThreadStart 和ParameterizedThreadStart 都是 委托类 ...

  3. [深入学习C#]C#实现多线程的方法:线程(Thread类)和线程池(ThreadPool)

    简介 使用线程的主要原因:应用程序中一些操作需要消耗一定的时间,比如对文件.数据库.网络的访问等等,而我们不希望用户一直等待到操作结束,而是在此同时可以进行一些其他的操作.  这就可以使用线程来实现. ...

  4. C#实现多线程的方法:线程(Thread类)和线程池(ThreadPool)

    简介 使用线程的主要原因:应用程序中一些操作需要消耗一定的时间,比如对文件.数据库.网络的访问等等,而我们不希望用户一直等待到操作结束,而是在此同时可以进行一些其他的操作.  这就可以使用线程来实现. ...

  5. 并发基础篇(六):线程Thread类的start()方法和run()方法【转载】

    [转载] 一.初识java的线程是通过java.lang.Thread类来实现的.VM启动时会有一个由主方法所定义的线程.可以通过创建Thread的实例来创建新的线程.每个线程都是通过某个特定Thre ...

  6. 线程 Thread类 GIL锁 信号量 Event事件

    线程的开启方法 进程是操作系统调度的最小单位,一个进程最少有一个主线程,而一个进程中可以开启多个线程 from threading import Thread def task(): print('A ...

  7. 线程Thread类

    进程:资源分配与调动的基本单位.如QQ.迅雷等每个独立运行的程序就是一个进程. 每一个进程可以有多个线程,如QQ可以收发信息.下载上传文件等. 多线程同时工作时,由CPU分配处理. public cl ...

  8. 多线程-2.线程创建方式和Thread类

    线程的创建方式 1.继承Thread类,重写run方法,示例如下: 1 class PrimeThread extends Thread { 2 long minPrime; 3 PrimeThrea ...

  9. Java 多线程之 Thread 类 和 Runnable 接口初步使用

    目录 Thread 类 Thread之定义线程类 Thread之开启线程 Runnable 接口 Runnable 之定义线程类 Runnable 之开启线程 @ Thread 类 Thread 类是 ...

随机推荐

  1. MySQL select join on 连表查询和自连接查询

    连表查询 JOIN ON 操作 描述 inner join 只返回匹配的值 right join 会从右表中返回所有的值, 即使左表中没有匹配 left join 会从左表中返回所有的值, 即使右表中 ...

  2. Java 使用 mail.jar 实现邮件发送

    目录 准备工作 使用到的 jar 包 实现代码 准备工作 要想实现邮件发送, 需要先打开发送邮箱的 POP3/SMTP 服务,打开方式在 设置>帐户 中去打开,打开之后如果是qq邮箱会获得一个授 ...

  3. MySQL全面瓦解17:触发器相关

    关于触发器 现实开发中我们经常会遇到这种情况,比如添加.删除和修改信息的时候需要记录日志,我们就要在完成常规的数据库逻辑操作之后再去写入日志表,这样变成了两步操作,更复杂了. 又比如删除一个人员信息的 ...

  4. Sqli - Labs 靶场笔记(一)

    Less - 1: 页面: URL: http://127.0.0.1/sqli-labs-master/Less-1/ 测试: 1.回显正常,说明不是数字型注入, http://127.0.0.1/ ...

  5. 二进制部署kubernetes

    Kubernetes二进制安装 环境准备: 主机环境:做好主机名hosts文件映射 硬件2cpu  2G内存 192.168.30.21 k8s-master 192.168.30.22 k8s-no ...

  6. 阿里云 RTC QoS 屏幕共享弱网优化之若干编码器相关优化

    屏幕共享是视频会议中使用频率最高的功能之一,但在实际场景中用户所处网络环境复杂,常遇到丢包或者拥塞的情况,所以如何优化弱网环境下的用户体验也成为了音视频通信中重要的一环.本文主要分享阿里云 RTC Q ...

  7. 用 CSS background 实现刻度线的呈现

    有的时候,我们需要在网页中的进度条或某种度量计上呈现一条条的刻度线.例如这种: 简单的实现方式,大致有两种:一是用图片做背景,横向平铺线条图片:二是给每一块刻度区域平铺一个元素,然后用边线实现.身为一 ...

  8. Redis 实战 —— 02. Redis 简单实践 - 文章投票

    需求 功能: P15 发布文章 获取文章 文章分组 投支持票 数值及限制条件 P15 如果一篇文章获得了至少 200 张支持票,那么这篇文章就是一篇有趣的文章 如果这个网站每天有 50 篇有趣的文章, ...

  9. RocketMQ—消息队列入门

    消息队列功能介绍 字面上说的消息队列是数据结构中"先进先出"的一种数据结构,但是如果要求消除单点故障,保证消息传输可靠性,应对大流量的冲击,对消息队列的要求就很高了.现在互联网的& ...

  10. PAT Advanced 1004 Counting Leaves

    题目与翻译 1004 Counting Leaves 数树叶 (30分) A family hierarchy is usually presented by a pedigree tree. You ...