第二十三节: EF性能篇(三)之基于开源组件 Z.EntityFrameWork.Plus.EF6解决EF性能问题
一. 开篇说明
EF的性能问题一直以来经常被人所吐槽,究其原因在于“复杂的操作在生成SQL阶段耗时长,且执行效率不高”,但并不是没有办法解决,从EF本身举几个简单的优化例子:
①:如果仅是查询数据,并不对数据进行增、删、改操作,查询数据的时候可以取消状态追踪。
db.TestInfor.AsNoTracking().FirstOrDefault();
②:用什么查什么,比如一张表有100多个字段,本次业务只需要5个字段,一定是select这5个字段,然后toList,而不是全部查询,再toList()
③:利用EF调用原生SQL语句或者EF调用存储过程执行。 (目前为止,没有发现该方式存在什么问题,而且性能也很快,广大博友如果认为这种方式存在什么问题,可以留言给我普及扫盲一下)
以上的几种方式,或许在一定程度上能解决一些问题,但面对大数据量的增、删、改,还是心有力而力不足。
1. 前面的章节
前面的章节提到了Z.EntityFramework.Extensions 插件解决EF性能问题,该插件确实很nb,性能很高,而且功能很全,但是呵呵,天上没有掉馅饼的好事,该插件是收费的,如果你公司不差钱,或者你是土豪,那么强烈推荐使用该插件,性能确实不错,并且你可以直接右上角 x,不需要看该篇文章了^_^。
但往往现实是残酷,穷人居多,这个时候就需要找免费的解决方案了,前面章节提到了 SqlBulkCopy 类(与EF没有半毛钱关系),它可以实现增加操作,不得不说,它在处理大数据量的增加的时候,确实很出色!!!。
那么删除和更新怎么办呢?
答案是:可以借助 Z.EntityFrameWork.Plus.EF6 才解决。
2. 进入主题
Z.EntityFrameWork.Plus.EF6 和 Z.EntityFramework.Extensions 是同一公司的产物,该插件支持的功能很多,比如 删除、更新、缓存机制、过滤器等等,但唯独没有新增操作(都懂得,什么功能都有的话,他的兄弟 Z.EntityFramework.Extensions 怎么办?)。
本章节仅介绍删除和更新两个最常用的功能。
该插件的几点说明:
①:仅支持EF5、EF6、EF Core,注意不同的版本对应该插件的后缀不同,该章节使用的是EF 6.2,所以对应 Z.EntityFrameWork.Plus.EF6
②:官方号称:Improve EF Performance by 2000%
③:可以通过Nuget进行安装
④:文档地址 : http://entityframework-plus.net/batch-delete
GitHub地址: https://github.com/zzzprojects/EntityFramework-Plus
3. 数据库准备
二. 删除相关
1. Delete() 同步删除方法
2. DeleteAsync() 异步删除方法 <根据实际业务场景选择使用>
3. BatchSize:批次大小
Delete和DeleteAsync两个删除方法都可以设置该参数的值:x => x.BatchSize,该参数表示一次执行的条数,默认值为4000,比如你要删除4w条数据,默认值的话,就要删除10次,
适当的提高该值,会增加删除效率,但并不代表无限增大。
特别注意:下面测试使用的Delete方法是默认块级大小4000的情况下进行测试,后面把BatchSize直接改为8w,删除8w条数据在1.6s左右
4:BatchDelayInterval:批次执行的时间间隔
比如BatchSize=4000,BatchDelayInterval=1000,删除4w条数据,表示的意思是删除4000的时候等待1s,然后再删除。
PS:该参数不是很常用,适用于你既需要删除很多数据,而且在批处理之间的暂停间隔继续执行CRUD操作
5:Executing:执行删除命令之前,去执行一段命令文本
PS:根据实际场景选择使用。
下面进行性能测试:(1w条、 4w条、 8w条数据的删除操作)
(1). EF原生删除代码
1 ///2 /// EF普通方法测试性能 3 /// 4 /// 5 public static void DeleteCommon1(DbContext db) 6 { 7 Console.WriteLine("---------------------调用普通方法1删除--------------------------------"); 8 var list=db.Set.Where(u=>u.id!="1").ToList();
9 Stopwatch watch = Stopwatch.StartNew();
10 foreach (var item in list) 11 { 12 db.Entry(item).State = EntityState.Deleted; 13 } 14 int count = db.SaveChanges(); 15 watch.Stop(); 16 Console.WriteLine($"{count}条数据耗时:{watch.ElapsedMilliseconds}"); 17 }
(2). EF调用SQL语句的代码
1 ///2 /// EF调用SQL语句测试删除 3 /// 4 /// 5 public static async void DeleteCommon2(DbContext db) 6 { 7 Stopwatch watch = Stopwatch.StartNew(); 8 string sql = "delete from TestTwo where id !='1' "; 9 int count = 0; 10 //加上await,表示在这一步上异步方法执行完 11 var response = await db.Database.ExecuteSqlCommandAsync(sql); 12 count = response; 13 Console.WriteLine("异步方法已经开始执行,请耐心等待"); 14 watch.Stop(); 15 Console.WriteLine($"{count}条数据耗时:{watch.ElapsedMilliseconds}"); 16 }
(3). 利用该插件扩展的代码
1 public static void DeletePlus(DbContext db) 2 { 3 Console.WriteLine("---------------------调用扩展方法删除--------------------------------"); 4 Stopwatch watch = Stopwatch.StartNew(); 5 int count = db.Set().Where(u => u.id != "1").Delete(); 6 //设置块级大小(默认4000) 7 //int count = db.Set ().Where(u => u.id != "1").Delete(u => u.BatchSize = 80000); 8 watch.Stop(); 9 Console.WriteLine($"{count}条数据耗时:{watch.ElapsedMilliseconds}"); 10 }
最终的测试结论(下面的时间是取三次结果的平均值):
1w条数据 4w条数据 8w条数据
EF原生删除 76s 累哭了 累哭了
EF调SQL语句 1.152s 1.232s 1.558s
Z.Plus(默认块) 1.307s 1.982s 2.675s
最终结论: Z.EntityFrameWork.Plus.EF6的删除比EF原生要快的多! 但EF直接调用SQL语句貌似更快哈。
三. 更新相关
有了上面删除的基础,这里的更新操作就容易的多,更新的性能提升与删除类似,这里不再单独测试了,下面简单粗暴,直接介绍用法。
1. Update() 同步更新方法
2. UpdateAsync() 异步更新方法
3. Executing:上述两个方法的一个参数,表示执行更新命令之前,去执行一段命令文本(根据实际情况选择使用)
直接上代码:
1 public static void UpdatePlus(DbContext db) 2 { 3 Console.WriteLine("---------------------调用扩展方法更新--------------------------------"); 4 Stopwatch watch = Stopwatch.StartNew(); 5 int count = db.Set().Where(u => u.id != "1").Update(x => new TestTwo() 6 { 7 t21 = "0", 8 t22 = "1" 9 }); 10 watch.Stop(); 11 Console.WriteLine($"{count}条数据耗时:{watch.ElapsedMilliseconds}"); 12 }
综述:该插件的使用非常简单,在使用上,可以说没有任何难度可言,很多情况下,并不是你不会解决,而是你缺少一双善于发现的眼镜。
免费的大数据解决方案: SqlBulkCopy + Z.EntityFrameWork.Plus + EF调用SQL语句/存储过程 或许是一个不错的选择。
如果你对EF感兴趣,可以关注该章节:ORM系列之Entity FrameWork详解(持续更新)
第四节:一些指令总结
一. NuGet
1. 获取当前项目已经安装的类库:Get-Package
2. 安装指定版本:install-package <程序包名> -version <版本号>
3. 安装最新版本:install-package <程序名>
4. 正常卸载:uninstall-package <程序包名>
5. 强制卸载:uninstall-package <程序包名> -Force
6. 更新到最新版本:update-package <程序包名>
7. 更新到指定版本:update-package <程序包名> -version <版本号>
8. 重新安装所有Nuget包(整个解决方案都会重新安装):update-package –reinstall
9. 重新安装指定项目所有Nuget包:update-package -project <项目名称> -reinstall
在.Net FrameWork时代,Nuget的包都随着项目一起去存储,但进入.Net Core时代,所有的包都放在 C:\Users\yaopengfei\.nuget\packages 这个路径下,不在需要反复下载了,只有发布的时候才会进入程序。
对于应用程序引用的Framework包,微软提供了运行时包仓(runtime package store),所有应用程序共用运行时包仓,我们在发布应用程序的时候不需要关心,只要关注第三方包即可。
运行时包仓路径一般为:
linux:/usr/local/share/dotnet/store
windows: C:/Program Files/dotnet/store
二.
1. 查看.Net 版本:dotnet --version
定时调度系列之Quartz.Net详解
一. 背景
我们在日常开发中,可能你会遇到这样的需求:"每个月的3号给用户发信息,提醒用户XXX "、"每天的0点需要统计前一天的考勤记录"、"每个月的1号计算上个月的库存情况"、"定时初始化数据供其它业务使用"、"每隔2分钟轮询查数据库看某业务是否被审核通过,并提示用户" 等等。
以上需求在开发中都非常常见,但它们仅仅属于低端一点的需求,稍高端一点的需求比如:" 客服派车给调度,如果调度3天内没有执行任何操作,需要提示调度要抓紧派车了 ",到这一步为止看起来和上面的需求并没有什么两样,但如果我要求,系统管理员可以动态配置提示时间呢?即管理员可以配置调度几天没有执行任何操作,系统需要给出提示。这么一改的话,对于一些新手而已,难度就陡然上升了。
下面我们接着升级需求,系统中有A、B、C、D。。。。等等多个业务,系统管理员可以动态配置每个业务的执行时间情况(如:每隔2s执行一次、每月3号执行一次等等),并且可以动态的控制每个业务的开启、关闭、暂停、全部关闭、全部暂停等。需求升级到这个程度,估计新手就抓虾了,有一定经验的人但是没有接触过类似框架,面对这样的需求,也需要一点时间去研究。
解惑:
需求1:也就是我们常说的定时任务,简单一点的可以借助Timer类来实现,对时间要求复杂的需要借助第三方的框架来实现,如:Quartz.Net.
需求2:需要我们动态配置定时任务的执行时间。
需求3:需要我们自己搭建一个定时调度框架,然后来动态配置任务的开启、关闭和触发器的情况。
需求三就是我们最终的目标,即我们要搭建一套通用的定时调度框架,可以手动增加定时任务,配置其触发器,手动控制任务的开启、关闭(B/S 架构);或者C/S架构,最终部署成windows服务的形式。
综上所述:Quartz.Net将是我们的最佳选择。
二. Quartz.Net简介
1. Quartz.Net是一个强大、开源、轻量的作业调度框架,是 OpenSymphony 的 Quartz API 的.NET移植,用C#改写,可用于winform和asp.net应用中。它灵活而不复杂。你能够用它来为执行一个作业而创建简单的或复杂的作业调度。
它有几大优势:
a.持久化数据库:SQLServer、MySQL、MongoDB、Redis
b.集群支持:双机热备
c.支持Web B/S架构和WinForm C/S架构
d.trigger可以灵活的控制时间各类苛刻的时间要求
2. Quartz.Net框架的使用
①.引入方式:
a. 通过NuGet引入搜索Quartz引入相应的程序集。
(2.x版本需要引入三个程序集:Common.Logging、Common.Logging.Core、Quartz和一个xsd文件(用于xml配置进行代码提示)
b. 去官网下载源码,通过引入源码的形式进行使用(http://www.quartz-scheduler.net/)。
PS:目前最新版本为3.0.5(2018-06-23),这里采用的是2.6.1 即2x的最后一个版本,3x起支持.netcore,且基本用法发生一些变化,最好使用vs2017
②.基本使用:
a.创建作业调度池(Scheduler)
b.创建一个具体的作业即job (具体的job需要单独在一个文件中执行)
c.创建并配置一个触发器即trigger
d.将job和trigger加入到作业调度池中
e.开始调度 start
3. 官方地址
官网:http://www.quartz-scheduler.net/
示例:http://www.quartz-scheduler.net/documentation/quartz-2.x/quick-start.html
三. 系列章节
下面我们将一起通过以下几个章节,系统的学习一下Quartz.Net的基本用法、扩展一些高级配置,并且最终从框架的角度来处理定时调度这一类通用的问题,欢迎广大博友前来交流、指正、学习。
第一节: Timer的定时任务的复习、Quartz.Net的入门使用、Aop思想的体现 :
第二节: 比较DateTime和DateTimeOffset两种时间类型并介绍Quartz.Net中用到的几类时间形式(定点、四舍五入、倍数、递增)
第三节: Quartz.Net五大构件之Scheduler(创建、封装、基本方法)和Job(创建、关联等) :
第四节: Quartz.Net五大构件之Trigger通用用法(常用方法、优先级、与job关联等) :
第五节: Quartz.Net五大构件之Trigger的四大触发类 :
第六节: 六类Calander处理六种不同的时间场景 :
第七节:Trigger(SimpleTrigger、CronTrigger)哑火(MisFire)策略 :
第八节: Quartz.Net五大构件之SimpleThreadPool及其四种配置方案 :
第九节: 利用RemoteSheduler实现Scheduler跨服务器的远程控制 :
第十节: 利用SQLServer实现Quartz的持久化和双机热备的集群模式 :
第十一节: 封装通用的定时调度框架,实现新增、删除、开启、暂停计划任务(未完):
第十二节: 总结Quartz.Net几种部署模式(IIS、Exe、服务部署【借助TopSelf、服务类】) :
第十七节:易混淆的概念(静态和非静态、拆箱和装箱)
一. 静态和非静态
1. 概念介绍
① 静态类(被static修饰) vs 普通类(没有被static修饰)
② 静态成员:被static修饰的成员,比如:静态方法、静态字段等
③ 普通成员(实例成员):不被static修饰的成员,比如:普通方法、普通字段
2. 运行机制
① 静态成员在程序运行的时候会“先于”实例成员被加载到内存中,静态成员不需要单独创建,当然静态类也不能被实例化。
比如:静态字段和静态构造函数只有在程序第一次使用该类之前被调用,而且只能调用一次,利用该特性,可以设计单例模式。
补充单例模式的代码:
1 public class STwo 2 { 3 ///4 /// 模拟耗时的构造函数 5 /// 6 private STwo() 7 { 8 long result = 0; 9 for (int i = 0; i < 1000000; i++) 10 { 11 result += i; 12 } 13 Thread.Sleep(1000); 14 Console.WriteLine("{0}被构造...", this.GetType().Name); 15 } 16 17 private static STwo _STwo = null; 18 ///19 /// 静态的构造函数:只能有一个,且是无参数的 20 /// 由CLR保证,只有在程序第一次使用该类之前被调用,而且只能调用一次 21 /// 22 static STwo() 23 { 24 _STwo = new STwo(); 25 } 26 27 public static STwo CreateIntance() 28 { 29 return _STwo; 30 } 31 }
1 public class SThird 2 { 3 ///4 /// 模拟耗时的构造函数 5 /// 6 private SThird() 7 { 8 long result = 0; 9 for (int i = 0; i < 1000000; i++) 10 { 11 result += i; 12 } 13 Thread.Sleep(1000); 14 Console.WriteLine("{0}被构造...", this.GetType().Name); 15 } 16 ///17 /// 静态变量:由CLR保证,在程序第一次使用该类之前被调用,而且只调用一次 18 /// 19 private static SThird _SThird = new SThird(); 20 21 public static SThird CreateIntance() 22 { 23 return _SThird; 24 } 25 }
② 实例成员:只有创建了对象(即进行了类的实例化)才会存在于内存中。
证明:在StaticInstroduceDemo类中的静态变量a上加断点(方法体内部加断点,两次实例化类的时候加断点),然后在客户端实例化两次 StaticInstroduceDemo类,分别调用ShowStaticInstroduce方法,
发现:第一次实例化的时候进入静态变量a上的断点,然后在调用对应的方法,而第二次实例化的时候不再进入静态变量a上的断点,直接进入调用的方法。从而验证了:静态成员优先于实例成员进入内存,且只在第一次使用该类的时候进行初始化分配内存,后续将不在分配.
3. 基于以上运行机制可以得出以下几个结论
① 普通类:
a. 普通类中可以存在静态成员(静态方法、静态字段),但里面的静态方法不能调用普通类中的普通字段<普通类没有实例化的话,普通字段是不存在的>。
b. 普通类中普通方法可以调用里面的静态字段<静态成员先于实例成员加载到内存中>
eg:
② 静态类:
静态类中只能存在静态成员(静态方法和静态字段)
4. 调用形式
① 静态成员: 类名.静态成员名
② 实例成员: 实例名.实例成员名
5. 声明周期
① 对于C/S程序:每启动一次,相当于一次生命周期,关闭程序生命周期结束,多次打开客户端程序互不干扰。
验证:上述的ShowStaticInstroduce方法,两次实例化后调用输出的结果是2,3 。此时我再打开一个客户端,结果依旧是2,3,这也很好证明了声明周期的问题。
② 对于B/S程序:static修饰的成员存储在服务器端中,与客户端关闭与否无关。《详见HomeController下的TestStatic方法》
验证:打开不同浏览器,分别调用TestStatic1方法,发现每点击一次按钮,返回值增加1,关闭该浏览器,重新点击,返回值在原基础上加1. 关闭IIS重新运行,返回值重新计数。证明:在B/S模式下,static修饰的成员存储在服务器端内存中,与客户端关闭与否无关。
6. 使用场景
① 对于C/S程序:static修饰的变量可以当作缓存来使用。
② 对于B/S程序:可以利用static的特性来设计单例模式,或者面向多线程存储数据,进行资源的共享<PS: 不考虑性能方面问题和一些极端情况>。
③ 作为工具类,全局资源共享。
二. 拆箱和装箱
1. 补充两个概念:
值类型:int、double、char、bool、decimal、struct、enum
引用类型:各种class类、string、数组、接口、委托、object
2. 装箱:
将值类型→引用类型
3. 拆箱:
将引用类型→值类型
4. 经典面试题
请问下面代码涉及到几次拆箱和装箱。
分析:
① 第一次装箱发生在 object m2 = m1;
② 第一次拆箱发生在 (int)m2 上;
所以很多人认为答案是:1次装箱和1次拆箱,显然是不对的。
我们继续分析,熟悉 Console.WriteLine原理的知道内部调用string.Concat()方法进行拼接,而Contact有很多重载,F12看源码可知,
该案例只能使用 public static String Concat(object arg0, object arg1); 这个重载,
所以第2次装箱和第3次装箱发生在 m1→object 和(int)m2→object上。
所以最终答案是 1次拆箱和3次装箱
5. 特别注意:用什么类型进行装箱的,拆箱就拆成什么类型,否则会抛异常,无法进行类型转换。
PS:如果你对.Net其他知识感兴趣,可以参考 DotNet进阶系列(持续更新) ASP.NET MVC深入浅出系列(持续更新) ORM系列之Entity FrameWork详解(持续更新)
那些年我们一起追逐的多线程(Thread、ThreadPool、委托异步调用、Task/TaskFactory、Parallerl、async和await)
那些年我们一起追逐的多线程(Thread、ThreadPool、委托异步调用、Task/TaskFactory、Parallerl、async和await)
一. 背景
在刚接触开发的头几年里,说实话,根本不考虑多线程的这个问题,貌似那时候脑子里也有没有多线程的这个概念,所有的业务都是一个线程来处理,不考虑性能问题,当然也没有考虑多线程操作一条记录存在的并发问题,后面随着处理的系统业务越来越复杂,多线程再也回避不了了,也就借此机会深入研究了一下.Net中的多线程的处理方案。
发现在.Net领域领域中,多线程的处理大致经历了这么几个阶段:Thread→ThreadPool→委托的异步调用→Task→TaskFactory→Parallerl→异步编程模型(async和await)。
关注我博客的人会发现,早在2017年6月份的时候,就开始整理多线程问题了,大约用了6篇文章的来介绍了.Net中的线程的使用方法,主要是介绍相应类的实例方法的使用,有点帮助文档的意思了哦,最近多线程使用的相当频繁,借此机会重新结合一些实际业务系统介绍一下.Net领域的多线程问题,本次将整合原先的六篇文章(删除或覆盖更新)。
本质: 充分发掘CPU的性能,把一些并没有先后强依赖关系、且耗时代码块放到一个新的线程里去处理,那么原先按顺序执行的业务就会变成并行执行,让主线程继续往后执行,节约了时间了,提高了效率。
下面补充一下多线程在时间和空间上的开销:
(一). 时间上:
①:开启或销毁一个线程都会通知进出中的dll程序集,让这些dll进行相应的操作。
②:时间片切换:4个逻辑处理器(不考虑Inter的超线程技术,一核对多个线程),同时并行只能处理4个线程,多余的休眠,很多时候,我们看似很多线程在并行执行,实际上是间歇性的串行。
《关于这个说法有异议的话,请留下您的见解,欢迎讨论,请勿谩骂》
(二). 空间上:
①:用户模式堆栈,一个线程分配1M的堆栈空间。
②:内核模式的堆栈,用户模式的参数需要传递到内核模式。
③:线程的内核数据结构,会存放一下变量。
二. 概念的梳理
1. 进程、线程和多线程
进程:当一个程序开始运行时,它就是一个进程(或者多个,eg:游戏),进程包括运行中的程序和程序所使用到的内存和系统资源,而一个进程又是由多个线程组成。
线程:线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
2. 多线程的好处和弊端
好处:可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。(牺牲空间资源,来换取时间)
弊端:
①:线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;(占内存多)
②:多线程需要协调和管理,所以需要CPU时间跟踪线程; (占cpu多)
③:线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;(多线程存在资源共享问题)
④:线程太多会导致控制太复杂,最终可能造成很多Bug。(管理麻烦,产生意外bug)
3. 何时建议使用多线程
①. 当主线程试图执行冗长的操作,但系统会卡界面,体验非常不好,这时候可以开辟一个新线程,来处理这项冗长的工作。
②. 当请求别的数据库服务器、业务服务器等,可以开辟一个新线程,让主线程继续干别的事。
③. 利用多线程拆分复杂运算,提高计算速度。
4. 何时不建议使用多线程
当单线程能很好解决,就不要为了使用多线程而用多线程。
5. 同步调用和异步调用
①单线程同步调用:方法从上而下一次执行,一步一步执行,有先后顺序。
②异步调用(区别于异步方法):开启新的线程去执行业务,主线程单独执行,可以选择是否等待子线程执行完后再执行
同步方法 VS 异步方法:
1. 一个误区:异步方法指的是一些特有的方法(并不开启新线程),它和开启一个新的线程比如“很多情况下我们会说,开启一个新的线程去异步调用”,这不是一回事,典型的异步方法,比如js 的ajax请求。
2. 同步方法:我们平时封装的一些普通方法大多数都是同步方法,同步方法典型的特点:就是在没有得到方法的返回值或者该方法没有执行完,该调用就需要在这等待,不能继续执行。
3. 异步方法:异步方法在调用后,调用这在没有得到返回结果前,就可以继续执行后续业务,异步方法通常是通过通知、回调的方式告诉调用者,无须消耗过多的性能。
举例1:
$.Post("url",{},function(data){ });
$("#div1").html("");
这两行代码,第一行发送异步请求的时候,即使得到回调返回值,下面清空div1内容的操作同样也将执行,Post就是异步方法。
举例2:
先封装1个方法: function Add(a,b){ 先休眠5s; return a+b}
调用:
Add(1,2);
$("#div1").html("");
这两行代码,Add方法就属于同步方法,所以必须等5s后,Add方法执行完,才能执行下面清空div1内容的操作。
总结:同步方法和异步方法的区别就是:是否需要等待返回结果,才能执行后续操作。
6. 异步多线程的三个特点
①:同步方法卡界面,原因是主线程被占用;开启新线程去异步调用不卡界面,原因是计算交给了别的线程,主线程空闲.
②:同步方法慢,原因是只有一个线程计算;开启新线程去异步调用快,原因是多个线程同时计算,但是更消耗资源,不宜太多.
②:异步多线程是无序的,启动顺序不确定、执行时间不确定、结束时间不确定.
三. 系列章节
第一节:复习委托,并且通过委托的异步调用开启一个新线程和异步回调、异步等待。
第二节:深入剖析Thread的五大方法、数据槽、内存栅栏。
第三节:ThreadPool的线程开启、线程等待、线程池的设置、定时功能。
第四节:Task的启动的四种方式以及Task、TaskFactory的线程等待和线程延续的解决方案。
第五节:Task构造函数之TaskCreationOptions枚举处理父子线程之间的关系。
第六节:深入研究Task实例方法ContinueWith的参数TaskContinuationOptions。
第七节:利用CancellationTokenSource实现任务取消和利用CancellationToken类检测取消异常。
第八节:Task的各类Task
第九节:深究并行编程Parallel类中的三大方法 (For、ForEach、Invoke)和几大编程模型(SPM、APM、EAP、TAP)
第十节:利用async和await简化异步编程模式的几种写法
第十一节:深究用户模式锁的使用场景(异变结构、互锁、旋转锁)
第十二节:深究内核模式锁的使用场景(自动事件锁、手动事件锁、信号量、互斥锁、读写锁、动态锁)
第十三节:实际开发中使用最多的监视锁Monitor、lock语法糖的扩展、混合锁的使用(ManualResetEvent、SemaphoreSlim、ReaderWriterLockSlim)
第十四节: 介绍四大并发集合类并结合单例模式下的队列来说明线程安全和非安全的场景及补充性能调优问题。
第十五节:深入理解async和await的作用及各种适用场景和用法
第十六节:
第十七节: