C# 实现多线程启动停止暂停继续

前言

多线程编程是提升应用程序性能和响应能力的关键技术之一。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

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!

相关推荐
SomeB1oody3 分钟前
【Rust自学】7.3. use关键字 Pt.1:use的使用与as关键字
开发语言·后端·rust
❦丿多像灬笑话、℡12 分钟前
leetcode 热题100(208. 实现 Trie (前缀树))数组模拟c++
算法·leetcode·c#
minstbe13 分钟前
WEB开发 - Flask 入门:Jinja2 模板语法进阶 Python
后端·python·flask
无名之逆39 分钟前
lombok-macros
开发语言·windows·后端·算法·面试·rust·大学期末
m0_748247801 小时前
SpringBoot集成Flowable
java·spring boot·后端
散一世繁华,颠半世琉璃1 小时前
SpringBoot揭秘:URL与HTTP方法如何定位到Controller
spring boot·后端·http
安晴晚风2 小时前
从0开始在linux服务器上部署SpringBoot和Vue
linux·运维·前端·数据库·后端·运维开发
黄金小码农8 小时前
C# 2024/12/26 周四
c#
海绵波波1078 小时前
flask后端开发(10):问答平台项目结构搭建
后端·python·flask
网络风云9 小时前
【魅力golang】之-反射
开发语言·后端·golang