前言
多线程编程是提升应用程序性能和响应能力的关键技术之一。C# 提供了强大的多线程支持,能够轻松创建并发任务,优化资源利用,并改善用户体验。然而,实现多线程的同时,如何安全有效地管理这些线程(如启动、停止、暂停和继续)是一个重要的问题。
大部分初学者在学习C#上位机编程时,多线程是一个很难逾越的鸿沟,不合理地使用多线程,会导致经常出现各种奇怪的问题,这也是很多初学者不敢使用多线程的原因。但是在实际开发中,多线程是一个不可避免的技术栈,基本上每个项目都会使用到,因此学好多线程技术,很重要。
本文将深入探讨如何使用 C# 实现多线程的启动、停止、暂停和继续功能。我们将介绍相关的理论基础,分享实用代码示例,并讨论最佳实践和常见问题的解决方案。
多线程原理
什么是多线程?
多线程是一种编程技术,允许一个程序同时运行多个独立的执行流程,每个执行流程称为一个线程。通过这种方式,程序可以提高并发性和效率,更有效地利用系统资源。
单核CPU与多线程
想象一下创业初期的情景:你可能需要身兼多职,既要处理业务,又要负责技术支持,还要管理财务。虽然你看似在"同时"完成这些任务,但实际上你是通过高效的时间管理来快速切换不同职责,从而营造出多任务并行的假象。
这正是单核CPU实现多线程的方式------通过时间片轮转(Time-Slicing)机制,CPU在极短的时间间隔内(通常为10-100毫秒)快速切换不同的线程,使得用户感觉所有任务都在同时进行。
多核CPU与多线程
随着计算机技术的进步,现代CPU大多具备多个核心(如8核、16核等),每个核心都可以独立执行任务。这种多核架构真正实现了多线程的优势:多个线程可以在不同的核心上同时运行,从而使多段逻辑能够并行处理。
充分利用多核CPU的多线程能力,不仅可以显著提升应用程序的性能,还能最大限度地发挥硬件潜力。如果不使用多线程,就如同拥有一辆高性能跑车却只用于日常代步,未能充分发挥其优势。
多线程发展
多线程的重要性与挑战
尽管多线程技术能够显著提升代码的执行效率和CPU资源利用率,许多开发者仍然对其望而却步。主要原因在于,如果使用不当,多线程可能会引发各种难以调试的问题,如竞态条件、死锁和数据不一致等。
理解多线程的本质
重要的是要认识到,多线程本质上是"不可控"的。不应将其视为简单的开关机制------需要时开启,不需要时关闭。实际上,多线程的执行依赖于CPU调度器的决定。
当我们说"启动多线程"时,实际上是告诉操作系统这个线程可以运行了,但具体何时开始或停止,则由CPU根据当前系统状态来决定。因此,开发人员只能通过间接的方式控制线程的行为。
.NET 框架中的多线程演进
微软在多线程支持方面不断进步,.NET 框架也经历了多个版本的迭代:
.NET 1.0:引入了 Thread 类,提供了基本的多线程支持。
.NET 2.0:推出了 ThreadPool 线程池,提高了线程管理和资源利用的效率。
.NET 3.0:引入了 Task 类,简化了并发任务的管理,并逐渐成为多线程编程的最佳实践。
.NET 4.0:增加了 Parallel 类库,支持并行编程,进一步提升了复杂任务处理的能力。
.NET 4.5:引入了 async/await 关键字,使得异步编程更加简洁直观,极大地方便了编写非阻塞代码。
控制多线程的方法
.NET 框架提供的接口(方法)允许开发人员间接地控制多线程的启动、停止、暂停和继续。这些工具不仅简化了多线程编程,还提高了代码的安全性和可靠性。
例如:
启动线程:使用 Thread.Start() 或 Task.Run() 来启动新线程或任务。
停止线程:通过设置取消标记或使用 CancellationToken 安全终止线程。
暂停与继续线程:利用信号量、事件等待句柄 (EventWaitHandle) 和其他同步原语实现线程的暂停和恢复。
多线程启停
Task 类是 .NET 中用于处理多线程和异步操作的核心类之一,提供了丰富的 API 函数,使得多线程管理变得更加简单和直观。Task 支持多种启动方式,如 Task.Run、Task.Factory.StartNew 和 Start 等。
下面将以 Task.Run 为例,演示如何使用多线程实现一个简单的案例。
创建一个简单的程序,其中包含一个值类型的变量,该变量每间隔 100 毫秒自增一次,当达到某个设定值后重新从零开始计数,并将当前值显示在界面上。这个例子展示了如何在后台线程中执行重复任务,并安全地更新 UI 线程上的控件。
所以该任务执行代码如下:
我们可以看到在方法里调用了一个cts对象,这个对象就是CancellationTokenSource的对象,因此我们需要创建一个CancellationTokenSource对象cts,同时在属性CurrentValue中,要显示控件的值,这里需要用到委托实现跨线程访问的问题,这个我们后续专题讲解,代码如下:
然后在启动线程按钮的事件里,编写代码如下:
停止线程按钮的事件里,只需要调用cts的Cancel方法即可:
我们可以看到,这里就是通过cts来控制cts的IsCancellationRequested属性,进而实现多线程的控制,这里的cts.IsCancellationRequested类似于一个布尔类型的标志位,但是CancellationTokenSource的作用不仅如此,还可以在此基础上实现多线程超时判断,注册事件等更复杂的多线程操作。
多线程暂停继续
多线程的暂停继续,.NET为我们提供了另外一个对象------ManualResetEvent,这个对象会有一个值,这个值是布尔类型,就像一个门闸一样,True是打开门闸,False是关闭门闸,所以想要暂停多线程就调用这个对象的Reset方法,想要继续多线程就调用这个对象的Set方法,使用非常简单。首先我们创建一下这个对象,可以通过构造方法,给这个对象赋初始值,我这里为True,这样就能直接运行,不会阻塞,代码如下:
但是如果希望这个对象与多线程有所联系,必须要在多线程的方法里体现这个对象的作用,这个是调用这个对象的WaitOne方法,表示在调用的地方阻塞住,通过判断True或者False来决定是否继续执行,就像大家开车过高速收费站一样,即使现在普遍采用ETC了,在入口也需要减速,有一个ETC识别的过程,识别成功才会抬杆,识别不对,杆子是不会自动抬起的,这个是一样的道理。所以线程执行代码修改如下:
对比一下,其实就是加了一个manual.WaitOne()。线程暂停继续代码如下:
暂停继续的使用除了ManualResetEvent,还有一个AutoResetEvent,AutoResetEvent和ManualResetEvent的用法基本上是一样的,这里就不过多赘述,大家可以自己尝试一下。
这两者的区别在于一个是手动,一个是自动,AutoResetEvent会在置位之后自动复位,这样体现在多线程里,就是会只执行一次,就像大家进小区一样,如果有10辆车在排队,这时候如果自动模式,每次都要抬杆落杆,每次只允许进一辆车,如果是手动模式,可以由保安控制门闸打开,等10辆车都进去之后,再由保安将门闸关闭。
总结
多线程技术虽然强大,但也伴随着一定的复杂性和风险。理解其本质,并熟练掌握 .NET 框架提供的工具和最佳实践,可以帮助大家更好地面对这些问题,充分利用多核 CPU 的性能优势。随着 .NET 框架的不断演进,多线程编程变得越来越简单和安全,为程序开发提供了坚实的基础。
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
优秀是一种习惯,欢迎大家留言学习!
作者:上位机Guide
出处:mp.weixin.qq.com/s/MVU6umz9fPSnpazOqBkbvw
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!