C# 多线程入门系列(二)


线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。本文以一些简单的小例子,简述多线程的发展历程【Thread,ThreadPool,Task,Parallel】,仅供学习分享使用,如有不足之处,还请指正。

Thread

Thread做为早期【.Net Framework1.0】的.Net提供的多线程方案,提供了很多的封装方法,来操作线程。具体如下所示:

  1. Start方法,用于启动一个线程。线程的状态变更为Running。
  2. Suspend方法,挂起一个线程,或者如果线程状态为已挂起,则不起作用。
  3. Resume方法,如果线程为已挂起,这继续运行。
  4. Join方法,等待线程,直到线程结束,也可以设置等待时间。
  5. Abort方法,强制终止线程,跑出ThreadAbortException异常。
  6. 其他线程属性:IsBackground是否后台线程,IsThreadPoolThread是否线程池线程,IsAlive线程是否运行,Priority线程优先级,ThreadState当前线程状态,ManagedThreadId线程唯一标识等

通过Thread可以单独的开启一个线程,通过构造函数来创建线程对象,可以是无参数也可以是带参数。其中参数ThreadStart是一个无参数委托,ParameterizedThreadStart为一个带参数委托。

无参数,示例如下所示:

 1 private void btnThread_Click(object sender, EventArgs e)
 2 {
 3     ThreadStart threadStart = new ThreadStart(DoSomethingLong);
 4     Thread thread = new Thread(threadStart);
 5     thread.Start();
 6 }
 7 
 8 private void DoSomethingLong() {
 9     string name = "Thread";
10     Console.WriteLine("************DoSomethingLong 开始 name= {0} 线程ID= {1} 时间 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"));
11     //CPU计算累加和
12     long rest = 0;
13     for (int i = 0; i < 1000000000; i++)
14     {
15         rest += i;
16     }
17     Console.WriteLine("************DoSomethingLong 结束 name= {0} 线程ID= {1} 时间 = {2} 结果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest);
18 }

示例结果如下所示:

 

带参数示例,如下所示:

 1 private void btnThread2_Click(object sender, EventArgs e)
 2 {
 3     ParameterizedThreadStart threadStart = new ParameterizedThreadStart(DoSomethingLongWithParam);
 4     Thread thread = new Thread(threadStart);
 5     string name = "Param";
 6     thread.Start(name);
 7 }
 8 
 9 private void DoSomethingLongWithParam(object name) {
10     Console.WriteLine("************DoSomethingLongWithParam 开始 name= {0} 线程ID= {1} 时间 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"));
11     //CPU计算累加和
12     long rest = 0;
13     for (int i = 0; i < 1000000000; i++)
14     {
15         rest += i;
16     }
17     Console.WriteLine("************DoSomethingLongWithParam 结束 name= {0} 线程ID= {1} 时间 = {2} 结果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest);
18 }

带参数示例结果,如下所示:

通过对以上示例进行分析,得出结论如下所示:

  1. 线程是由操作系统进行创建的,Thread提供的方法只是对底层方法的封装。比如执行对应方法后,操作系统并不会立即执行相应的操作,而要CPU时间片轮转后才会执行响应。
  2. Thread创建线程过于松散,缺乏管理,例如,如果同时创建10000个线程,程序也不会报错,但是系统可能无法承载如此多的线程而导致崩溃。
  3. Thread的频繁创建和销毁,也会消耗系统资源。

ThreadPool

为了应对Thread创建缺乏管理的问题,在后续版本【.Net Framework2.0】中推出了线程池的概念。那什么是线程池呢?

池化资源管理设计思想:线程是一种资源,之前每次需要线程,都是去创建线程,使用完成后,再释放掉。池化,就是做一个容器,容器提前申请指定数量的线程,需要用到线程的时候,直接到线程池中取,用完之后再放回容器【通过控制状态标识线程是否正在被使用】。避免频繁的创建和销毁,容器还会根据限制的数量取申请和释放。

关于通过线程池创建多线程,具体示例如下:

1 private void btnThread3_Click(object sender, EventArgs e)
2 {
3     WaitCallback waitCallback = new WaitCallback(DoSomethingLongWithParam);
4     string name = "ThreadPool";
5     ThreadPool.QueueUserWorkItem(waitCallback,name);
6 }

线程池示例执行结果如下所示:

通过对线程池执行结果进行分析,得出结论如下:

  1. 两次执行结果,均为同一个线程ID,说明线程用完并未销毁,而是放回线程池子,待下次使用时重新取出,继续使用。
  2. 通过线程池可以有效的控制线程并发的数量,避免资源的浪费。
  3. 通过分析源码发现,ThreadPool线程池提供的接口较少,在线程等待和交互方面不太友好。

Task

随着.Net版本的演化,后续版本【.Net Framework3.0】推出了Task做为多线程解决方案。默认情况下,可以通过构造函数创建Task,示例如下:

1 private void btnTask_Click(object sender, EventArgs e)
2 {
3     Action action = new Action(DoSomethingLong);
4     Task task = new Task(action);
5     task.Start();
6 }

默认Task示例,执行结果如下:

通过对以上Task示例和源码进行分析,得出结论如下:

  1. Task产生的线程,全部都是线程池线程。
  2. Task提供的丰富的API,便于开发实践。

Parallel

Parallel提供对并行线程的支持,可以通过Parallel同时发起多个线程,在某些方面具有应用优势,默认示例如下所示:

1 private void btnParallel_Click(object sender, EventArgs e)
2 {
3     Action action = new Action(DoSomethingLong);
4     Parallel.Invoke(action,action,action);
5 }

Parallel的Invoke方法执行,结果如下:

通过对Parallel的Invoke示例方法进行分析,得出结论如下:

  1. Parallel的Invoke方法,可以同时开启多个线程,同时主线程【线程ID=1】也会参与计算,即页面也会卡住。
  2. Parallel可以通过ParallelOptions.MaxDegreeOfParallelism指定并发数量。

Task专讲

以下面的一个场景为例进行说明:

假如开发一个系统,流程如下:
1. 前期的需求调研,需求分析,系统设计,详细设计(顺序执行,是开发编码的前提)
2.按模块开发【中间阶段,可多人同时工作】
3.测试【顺序执行,是开发编码的后续工作】
分析:以上三个阶段,每一个阶段又可以细分数个小阶段,其中有些阶段是顺序执行的,有些阶段又可以并行执行。

以代码的形式进行描述,如下所示:

private void btnTask2_Click(object sender, EventArgs e)
{
    //开发前的工作
    Console.WriteLine("组建团队");
    Console.WriteLine("需求分析");
    Console.WriteLine("系统设计");
    Console.WriteLine("详细设计");
    //开始开发
    Task.Run(() => { Coding("张三", "接口"); });
    Task.Run(() => { Coding("李四", "前端页面"); });
    Task.Run(() => { Coding("王五", "手机App"); });
    Task.Run(() => { Coding("刘大", "后端业务"); });
    //开发后的工作
    Console.WriteLine("alpha测试");
    Console.WriteLine("beta测试");
    Console.WriteLine("uat测试");
    Console.WriteLine("系统上线");
}

private void Coding(string developer,string model) {
    Console.WriteLine("【Begin】在{0},{1}开始开发{2},线程id为{3}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), developer,model, Thread.CurrentThread.ManagedThreadId);
    Thread.Sleep(1000);
    Console.WriteLine("【 End 】在{0},{1}完成开发{2},线程id为{3}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), developer, model, Thread.CurrentThread.ManagedThreadId);
}

示例运行结果,如下所示:

 

 通过分析以上示例,发现程序并未按照预期的运行,很明显的一点:测试跑到了开发前面。

为了解决上述顺序错乱的问题,Task提供了WaitAll方法,如下所示:

 1 private void btnTask2_Click(object sender, EventArgs e)
 2 {
 3     //开发前的工作
 4     Console.WriteLine("组建团队");
 5     Console.WriteLine("需求分析");
 6     Console.WriteLine("系统设计");
 7     Console.WriteLine("详细设计");
 8     //开始开发
 9     List<Task> tasks = new List<Task>();
10     tasks.Add(Task.Run(() => { Coding("张三", "接口"); }));
11     tasks.Add(Task.Run(() => { Coding("李四", "前端页面"); }));
12     tasks.Add(Task.Run(() => { Coding("王五", "手机App"); }));
13     tasks.Add(Task.Run(() => { Coding("刘大", "后端业务"); }));
14     Task.WaitAll(tasks.ToArray());
15     //开发后的工作
16     Console.WriteLine("alpha测试");
17     Console.WriteLine("beta测试");
18     Console.WriteLine("uat测试");
19     Console.WriteLine("系统上线");
20 }

运行示例,结果如下所示:

 

 通过运行以上示例,发现:顺序确实符合预期,可以满足要求,但是程序会卡住,这点不太友好

如何才能优雅的控制先后顺序呢?Task还提供了TaskFactory,如下所示:

 1 private void btnTask2_Click(object sender, EventArgs e)
 2 {
 3     //开发前的工作
 4     Console.WriteLine("组建团队");
 5     Console.WriteLine("需求分析");
 6     Console.WriteLine("系统设计");
 7     Console.WriteLine("详细设计");
 8     //开始开发
 9     List<Task> tasks = new List<Task>();
10     tasks.Add(Task.Run(() => { Coding("张三", "接口"); }));
11     tasks.Add(Task.Run(() => { Coding("李四", "前端页面"); }));
12     tasks.Add(Task.Run(() => { Coding("王五", "手机App"); }));
13     tasks.Add(Task.Run(() => { Coding("刘大", "后端业务"); }));
14     TaskFactory taskFactory = new TaskFactory();
15     taskFactory.ContinueWhenAll(tasks.ToArray(), new Action<Task[]>((taskArray) => { 
16         //开发后的工作
17         Console.WriteLine("alpha测试");
18         Console.WriteLine("beta测试");
19         Console.WriteLine("uat测试");
20         Console.WriteLine("系统上线");
21     }));
22     
23 }

TaskFactory示例测试,如下所示:

 

 通过对示例进行分析,得出如下结论:

  1. 业务逻辑上已要求,页面也不会卡顿,优雅的实现了多线程的操作。
  2. Task产生的线程,为线程池线程。
  3. TaskFactory不仅提供了ContinueWhenAll等待所有线程,还提供了ContinueWhenAny等待任意线程。

备注

以上就是多线程的基础知识,旨在抛砖引玉,一起学习,共同进步。

古从军行【作者】李颀 【朝代】唐

白日登山望烽火,黄昏饮马傍交河。行人刁斗风沙暗,公主琵琶幽怨多。

野云万里无城郭,雨雪纷纷连大漠。胡雁哀鸣夜夜飞,胡儿眼泪双双落。

闻道玉门犹被遮,应将性命逐轻车。年年战骨埋荒外,空见蒲桃入汉家。

 

文章来源:https://www.cnblogs.com/hsiang/p/15690604.html

版权声明:本文为YES开发框架网发布内容,转载请附上原文出处连接
管理员
上一篇:.NET Core 实现动态代理做AOP(面向切面编程)
下一篇:模仿写了一个摸鱼应用解决原作者的问题
评论列表

发表评论

评论内容
昵称:
验证码:
验证码
关联文章

C# 线入门系列
C# 线入门系列(三)
C# 线入门系列(一)
C# List<T>线安全
SQLite Database 线访问需要注意的问题
Python线中试用wmi报错
C#线下载图片 URL转Image
.Net 线安全集合
C# Abp框架入门系列文章(一)
ManualResetEvent实现线的暂停与恢复
Python退出主进程后子线不会退出的解决方案
C# Socket网络编程 系列课程
Elasticsearch使用系列-ES简介和环境搭建
C#基础系列-过滤器与特性
使用 WPF 做个 PowerPoint 系列 基于 OpenXML 解析实现 PPT 文本描边效果
基于欧姆龙PLC#FinsTcp协议上位机通讯()-C#通讯模块开发
关于元插值问题的探讨
C# ThoughtWorks.QRCode 维码生成和解析
记一次本地正常上线接口报404
C# ASP.NET Core开发学生信息管理系统(

热门标签
.NET Core .NET Reactor ag-grid AI发布 api安全 ASP.NET Core C#DLL加密 C#播放声音 C#代码混淆 C#代码加密 ChromeDriver Codex DateTime DBeaver devexpress devTool DLL混淆 edge.js EF EFCore Electron element-ui el-form el-table excel FastReport FileStream FolderBrowerDialog FolderSelectDialog form提交 git gridcontrol gridview input javascript json字符串 JS转换对象JSON jwt JWT授权 linq log Math MCP mitmproxy MVC MySQL Navicat netstat nginx node_modules NSwag Nuget Nuget镜像 number PowerShell pyinstaller python pythoncom python爬虫 python抓包 pywin32 redis Requests-html RestSharp Selenium sql SQL Server Swagger to-cms Visual Studio VSCode vue VueRouter vue路由 VUE页面通讯 Webpack Windows Windows服务 winform wmi xlrd yaml YESCMS YESWEB开发框架 白象 表单提交 播放声音 打开URL 代码混淆 弹窗提醒 端口占用 对象转换 分布式 公共字典 机器码 进程排查 静态资源 开发指南 路由参数 密钥 配置教程 配置文件 权限 人工智能 任务 任务调度 日期间隔 日志 日志记录 省市区 授权验证 数据库 四舍五入 文案 文件读取 文件夹选择 文件目录选择 问题排查 行政区域数据 页面通讯 中间件 CSharp 事务锁 工单系统 并发控制 重复提交 CMS Markdig Markdown markdown-it marked 技术选型 VS Code 开发工具 源代码管理 版本控制 Docker PostgreSQL 时区 部署排查 CMS架构 EF Core 主题系统 二次开发 插件系统 容器 运维命令 镜像清理 Linux NAS 远程挂载 飞牛 fnOS S/4HANA SAP GUI SAP HANA SAP R/3 SAP入门 SAP版本 ERP SAP SAP MM 库存管理 物料管理 采购管理 入门教程 SAP S/4HANA SPRO 企业结构 采购组织 MM01 物料主数据 物料类型 BP分组 业务伙伴 供应商主数据 ME41 RFQ 库存物料 采购流程 ME51 消耗性物料 科目分配 采购申请 AC03 ML81N 外部服务 服务主数据 Business Partner SAP培训 ME51N MM模块 Lean Services MM-SRV 外部服务采购 PIR 供应来源 采购主数据 采购信息记录 ME31K 框架协议 计划协议 采购合同 ME01 供应来源确定 货源清单 MEQ1 供应源确定 配额安排 配额评分 MD04 MD21 MRP 计划文件 需求计划 批量程序 MD01N MD02 MRP Live MD05 MM 物料计划 优化采购 供应源 采购订单 ME2A 供应商确认 采购监控 Flexible Workflow 凭证释放 采购审批 释放策略 实地盘点 物料凭证 货物移动 MIGO 收货 移动类型 已撤回 供应商退货 货物发出 STO 库存转储 转移过账 生产订单 预留 GR/IR MIRO 供应商发票 物流发票校验 OMR2 税码 FI PP SD 实操教程 MRBR OMR6 发票差异 交货成本 后续借记 MI01 实物盘点 盘点差异 公司代码 工厂 组织结构 OMS2 主数据定制 自动科目确定 BP角色 CVI 伙伴确定 编号范围 凭证类型 字段选择 FBN1 OMBT OMC2 会计凭证 OMJJ BOM 委外加工 项目类别L MRKO 供应商寄售 特殊库存K MRKON PIPE Pipeline 特殊库存P ERS MRIS 发票计划 周期性结算 里程碑付款 变更追踪 版本管理 采购凭证 SFTP WebDAV 网盘 飞牛fnOS AMPL HERS MPN 中文教程 库存确定 可用性检查 缺件检查 Output Management 消息确定 输出确定 分割评估 库存计价 评估类别 评估类型 PB00 RM0000 条件技术 采购定价 MM-FI集成 OBYC 库存估价 文本类型 文本采用 EFB EVO MSV SU3 用户参数 发票校验 合同参照 履约保留款 特别总账 预付款 Fiori Launchpad SAP Fiori 应用导航 用户体验 LSMW LTMC Migration Cockpit 数据迁移 BRFplus OPD Output Control My Inbox 审批流程 灵活工作流 SAP PP 外部加工 SAP QM 检验批 质量信息记录 采购收货 SAP PM 维护BOM 维护订单 SAP SD SAP Service 端到端流程 MM模块培训 FI-MM集成 供应商管理 审批配置 FICO入门 SAP FICO 财务配置 供应商税务 预扣税 House Bank 银行对账 客户清账 应收账款 FI控制 验证与替代 印度 GST 税务配置 F110 FBZP EWM入门 SAP EWM 仓库管理 OX14 成本核算 物料评估 后勤配置 物料组 价值更新 数量更新 PP-PI 流程制造 生产计划 容差配置 SAP事务码 SAP基础 TCODE Basis 事务代码 MMNR 编号区间 采购实操 组织架构 OMSF SAP实操 FI配置 端口修改 密码设置 数据库配置 远程访问 ABAP基础 SAP ABAP 内表 变量定义 常量 数据类型 ABAP SAP开发 变量 基础语法 系统变量 结构体 字符串处理 循环语句 控制语句 DDIC SE11 数据字典 透明表 ABAP开发 ALE EDI IDoc 增强技术 ABAP内表 HASHED TABLE SORTED TABLE STANDARD TABLE 基本概念 性能优化 PARAMETERS SELECT-OPTIONS SELECTION-SCREEN 报表程序 选择屏幕 F4帮助 Report事件 输入校验 ABAP SQL ABAP语法 Open SQL SELECT 数据库访问 IKuai IP修改 PVE 网络配置 虚拟化
联系我们
联系电话:15090125178(微信同号)
电子邮箱:garson_zhang@163.com
站长微信二维码
微信二维码