C#线程Thread类
在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类的更多相关文章
- 并发基础(六) 线程Thread类的start()和run()
start()和run()方法对于刚接触线程的人来说,会有点混淆,有点难理解,一般都会有以下疑问: 一.start( )方法 1.为什么需要start方法:它的作用是什么: start方法的作用就是将 ...
- 线程 Thread类 的四个构造方法简介
在线程中,Thread类有四个构造方法: 当我们使用 Thread类创建对象的时候,传入参数,就会用到构造方法.ThreadStart 和ParameterizedThreadStart 都是 委托类 ...
- [深入学习C#]C#实现多线程的方法:线程(Thread类)和线程池(ThreadPool)
简介 使用线程的主要原因:应用程序中一些操作需要消耗一定的时间,比如对文件.数据库.网络的访问等等,而我们不希望用户一直等待到操作结束,而是在此同时可以进行一些其他的操作. 这就可以使用线程来实现. ...
- C#实现多线程的方法:线程(Thread类)和线程池(ThreadPool)
简介 使用线程的主要原因:应用程序中一些操作需要消耗一定的时间,比如对文件.数据库.网络的访问等等,而我们不希望用户一直等待到操作结束,而是在此同时可以进行一些其他的操作. 这就可以使用线程来实现. ...
- 并发基础篇(六):线程Thread类的start()方法和run()方法【转载】
[转载] 一.初识java的线程是通过java.lang.Thread类来实现的.VM启动时会有一个由主方法所定义的线程.可以通过创建Thread的实例来创建新的线程.每个线程都是通过某个特定Thre ...
- 线程 Thread类 GIL锁 信号量 Event事件
线程的开启方法 进程是操作系统调度的最小单位,一个进程最少有一个主线程,而一个进程中可以开启多个线程 from threading import Thread def task(): print('A ...
- 线程Thread类
进程:资源分配与调动的基本单位.如QQ.迅雷等每个独立运行的程序就是一个进程. 每一个进程可以有多个线程,如QQ可以收发信息.下载上传文件等. 多线程同时工作时,由CPU分配处理. public cl ...
- 多线程-2.线程创建方式和Thread类
线程的创建方式 1.继承Thread类,重写run方法,示例如下: 1 class PrimeThread extends Thread { 2 long minPrime; 3 PrimeThrea ...
- Java 多线程之 Thread 类 和 Runnable 接口初步使用
目录 Thread 类 Thread之定义线程类 Thread之开启线程 Runnable 接口 Runnable 之定义线程类 Runnable 之开启线程 @ Thread 类 Thread 类是 ...
随机推荐
- MoChat - 国内首款完全开源的 PHP 企业微信管理系统正式发布
MoChat -- 让企业微信开发更简单 项目地址 Github: https://github.com/mochat-cloud/mochat Gitee: https://gitee.com/mo ...
- 【win10】win10下两个显示器不同桌面壁纸
win10系统下,双屏显示为不同的桌面壁纸 操作: 1.鼠标右键点击个性化 2.点击背景选项 3.在图片上右键选择要添加为背景的图片 同理,将另一个屏幕壁纸设为监视器1 最后效果为两个分屏为不同桌面壁 ...
- Springboot之文件监控
背景:在实际环境部署构成中,由于特殊网络环境因素,有很多服务器之间的网络都是单向的,不能互相访问的,只有通过特定技术手段做到文件的单项摆渡,这就需要在两台服务器上分别写序列化程序和反序列化程序,这里不 ...
- Electron实用技巧-开机启动时隐藏主窗口,只显示系统托盘
# 1 在桌面软件中,开机自启动是很常见的功能,在electron中也提供了很好的支持,以下是主要代码: //应用是否打包if (app.isPackaged) { //设置开机启动 app.se ...
- Hadoop 专栏 - MapReduce 入门
MapReduce的基本思想 先举一个简单的例子: 打个比方我们有三个人斗地主, 要数数牌够不够, 一种最简单的方法可以找一个人数数是不是有54张(传统单机计算); 还可以三个人各分一摞牌数各自的(M ...
- win32 修改Edit控件文本颜色与背景色
#define WM_CTLCOLORMSGBOX 0x0132 #define WM_CTLCOLOREDIT 0x0133 //编辑控件Edit #define WM_CTLCOLORLISTBO ...
- k8s之共享存储概述以及演示
共享存储机制 k8s对有状态的容器应用或者需要对数据进行持久化的应用,在之前的篇章说过,可以将容器内的目录挂载到宿主机的容器目录或者emptyDir临时存储卷. 另外,k8s还开放了两个资源,分别是P ...
- Transparent Gateway的使用方法
前言 使用Transparent Gateway(透明网关),建立ORACLE与SQLServer的连接. 实现功能:在ORACLE中查询SQLServer数据库的内容. 注:网上有ORACLE和SQ ...
- NFS存储迁移至GlusterFS
NFS存储迁移至GlusterFS 前提条件 为防止脑裂,建议使用最低3台节点制作3复制集的存储卷: 在进行存储迁移前,GluseterFS存储节点需先成为k8s集群中的node节点: 存储切换时请勿 ...
- argparse的简单使用
简单记录一下argparse的用法 这个是针对我做区块链的一些demo时需要用到的,仅把用到了的一些操作记录,argparse很强大,更多细致的操作可以参考:https://docs.python.o ...