Thread/ThreadPool/Task/TaskFactory
1、进程和线程的区别
进程是正在执行中的程序,进程中执行的每个任务即为一个线程;
线程属于进程;线程离不开委托;
NetFramework 1.0版本:
线程Thread是C#语言对操作计算机线程的一个封装类;
线程代码讲解
Thread thread = new Thread ();thread.Suspend();//暂停
thread.Resume();//让暂停的线程继续执行thread.Abort();//终止线程
thread.ResetAbort();//终止的线程继续执行;静态方法;Thread.Sleep();//等待
thread.Join(2000);//限时等待,过时不候;while(thread.ThreadState != ThreadState.Stopped)
{Thread.Sleep(2000);
}//线程的优先级
thread.Priority = ThreadPriority.Highest;//最好不要通过这种方式设置;只是从概念上增加优先级,计算机整体的具体优先级还是以实际为准;thread.IsBackgroud = true;//设置后台线程;当进程关闭,线程随之取消;
2、单线程
按照顺序一个一个执行
3、多线程
同时工作 同时工作 同时工作 同时工作 同时工作 同时工作
1.效率高
2.消耗资源高
3.各线程执行顺序 难以控制
4、多线程回调
如何控制 多线程 的执行顺序 --> 回调 (多线程实例:线程 2 需要使用 线程 1 中的结果)
//Thread中没有控制线程执行顺序的方法;但可以自己通过封装的方式实现线程回调//多线程实例:线程 2 需要使用 线程 1 中的结果;
private void main()
{//1.普通多线程按顺序执行ThreadStart threadStart = new ThreadStart (() =>Console.WriteLine("线程1"););Action action = () => Console.WriteLine("线程2");This.ThradWithCallBack(threadStart, action);//2.如果需要有返回值呢?Func<int> func= () => //定义了一个委托{return DateTime.Now.Year;};//int iResult = ThradWithReturn(func);Func<int> fResult = ThradWithReturn(func);int iResult = fResult.Invoke();}//使用自定义封装方式老进行多线程回调
private void ThradWithCallBack(ThreadStart threadStart,Action action)
{//开启一个线程去执行传进来的两个线程;ThreadStart start = () =>{threadStart.Invoke();//去执行线程action.Invoke();};new Thread(start).Start();
}private Func<T> ThradWithReturn<T>(Func<int> func)
{T t = default(T);ThreadStart thradStart = () =>{t = func.Invoke();}ThreadStart threadStart = new ThreadStart (thradStart);threadStart.Start();return new Func<T>(() => {//threadStart.join(1000);//可以添加限时等待,return t;});
}
5、线程池 ThreadPool
线程池中有很多创建好的线程,用的时候拿出来用,用完了或者不用了,就放回线程池;
private void main()
{Console.WriteLine("线程1-1");//1.普通多线程按顺序执行{WaitCallback waitCallback = new WaitCallback( o =>Console.WriteLine("线程2-1");)ThreadPool.QueueUserWorkItem(waitCallback,"线程池");//从线程池中获取线程}//2.回调(线程等待){ManualResetEvent mre = new ManualResetEvent(false);//设置中间变量mre的默认值为falseThreadPool.QueueUserWorkItem(o => {Console.WriteLine("线程o")mre.Set();});//从线程池中获取线程//线程等待mre.WaitOne();//等待上一个线程先执行;}Console.WriteLine("线程1-1");
}
6、Task
6.1 Task与TaskFactory 的区别?
Task是对线程的一个封装类,实际上是一个线程
TaskFactory :创建Task的地方;
总结:Task来自于ThreadPool
private void main()
{Console.WriteLine("线程1-1");//1.如何申请一个线程{Task task = new Task(()=>{Console.WriteLine("线程2");});task.Start();}{TaskFactory taskFactory = new TaskFactory();taskFactory.StartNew(() => {Console.WriteLine("线程3");});}{//申请线程ThreadPool.SetMaxThreads(8, 8);//设置最大线程数量,【全局的】;List<int> thradIds= new List<int>();List<Task> tasklist = new List<Task>();for(int i=0; i<50; i++){tasklist.Add(Task.Run(() => {//申请一个线程thradIds.Add("获取当前线程Id");Console.WriteLine("当前线程Id");}))}Task.WaitAll(tasklist.ToArray());//阻塞主线程,等待所有子线程完成任务后,主线程再继续;(会导致主界面卡顿)int iResult = threadIds.Distinct().Count();//iResult=8;------------------------//总结:Task来自于ThreadPool--------}{List<Task> tasklist = new List<Task>();//单线程进行讲课Teach("课程1");//Teach() 用户自定义方法Teach("课程2");Teach("课程3");Teach("课程4");Console.WriteLine("课程教学完毕,准备分配任务编写代码...");//讲完课之后,让多个人同时去编码(多线程)TaskFactory taskFactory = Task.Factory();tasklist.Add(taskFactory.StaretNew(() => Coding("同学1","后台管理")));//Coding()用户自定义方法;tasklist.Add(taskFactory.StaretNew(() => Coding("同学2","前台页面")));tasklist.Add(taskFactory.StaretNew(() => Coding("同学3","微服务架构的搭建")));tasklist.Add(taskFactory.StaretNew(() => Coding("同学4","小程序接口实现")));//所有同学都顺利完成,我们准备聚个餐,K个歌...{//1.Task.WaitAll()Task.WaitAll(tasklist.ToArray());//等待所有同学完成工作后,主线程才继续执行;会造成主线程中界面卡顿;Console.WriteLine($"所有项目搭建完毕,我们准备聚个餐,K个歌...");}{//2.taskFactory.ContinueWhenAll()//使用了回调,在一堆任务执行完毕之后,去申请一个新的线程来执行新的动作;taskFactory.ContinueWhenAll(tasklist.ToArray(),t => Console.WriteLine($"所有项目搭建完毕,我们准备聚个餐,K个歌..."));}{//3.taskFactory.ContinueWhenAny()当执行了某一个任务时,主线程就会继续执行了;taskFactory.ContinueWhenAny(tasklist.ToArray(),t => Console.WriteLine($"当某一个同学完成工作了,老师就准备环境的搭建"));}{//4.如何控制线程数量;//为什么要限制线程数量?因为要合理分配计算机系统资源;List<Task> tasklist = new List<Task>();for(int i=0 ; i<10000 ; i++){int k = 1;if(tasklist.Count(t => t.Status == TaskStatus.Running) >= 20)//如果线程数量为20了,则主线程开始阻塞,且必须等到有一个线程完成之后,再继续主线程{Task.WaitAny(tasklist.ToArray());//下面这句代码自行调试和添加条件;暂未修改tasklist = tasklist.Where(t => t.Status != TaskStatus.RanToCompletion && t.Status != TaskStatus.Canceled && t.Status != TaskStatus.Faulted).ToList();//保留未执行完毕的线程;RanToCompletion()是否完成;Canceled取消;Faulted异常;}//添加一个新的线程tasklist.Add(Task.Run(()={Thread.Sleep(2000);Console.WriteLine($"This is new Thread.");//容易直接卡死计算机;}));Console.WriteLine($"线程数量为={tasklist.Count()}");}}{//5.如果新申请的线程里面有返回值怎么获取呢?Func<int> func = ()=>{ //定义一个委托delegate,返回当前年份;Thread.Sleep(3000);return DateTime.Now.Year;};Task<int> task = new Task<int>(func);task.Start();int i = task.Result;//这里会阻塞页面}}Console.WriteLine("线程1-1");
}
Task.WaitAll()
与taskFactory.ContinueWhenAll()类似,但是Task.WaitAll()会阻塞主线程,造成卡顿;当所有线程完毕之后,主线程继续;
taskFactory.WaitAny()
主线程进行阻塞,当有一个线程执行完毕后,主线程继续执行;
taskFactory.ContinueWhenAll()
举例:某个主页会涉及到多个数据,数据1来源于DB,数据2来源于第三方,数据3来源于其他DB;
传统顺序:一个一个进行查找,比较耗时;如果每一步平均耗时1秒,那么传统顺序会耗时3秒
使用ContinueWhenAll的时候,系统会进行同时查找,总耗时为1秒;提高查询效率;
taskFactory.ContinueWhenAny()
举例:查找某个数据;
传统思路查找顺序:(1)找缓存 --> (2)找接口 --> (3)找DB
使用ContinueWhenAny的时候,创建了多个线程去不同的地方同时进行查找,当有一个完成查找之后,就可直接返回数据,页面进行相应;
7、表达式目录树 Expression<>
表达式目录树 实际上是一个数据结构(二叉树)。
为什么使用表达式目录树?
答:表达式目录树 – 动态的
初级阶段:数据库查询的时候,都是拼接SQL语句(多一个条件就拼接一个查询字符串);
现在:使用LinqToSql;
Expression<Func<int, int, int>> ex1 = (m, n) => m * n + 2 + 3;//如下图所示
//1.委托
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;//(m, n) => m * n + 2实际上是一个匿名方法;Func<返回值, m, n>
var func1 = exp.Compile();
int IResult = func1.Invoke(1, 2);//2.表达式目录
var peopleQuery = new List<People>().AsQueryable();
Expression<Func<People, bool>> expression = p => p.Id.Tostring().Equals("5");
PeopleQuery.Where(expression);
//进行编译
//通过ILspy反编译
//看中间语言,经过解析后,可以如下表示;
ParameterExperssion p = Experssion.Paramter(typeof(People),"p");
FieldInfo fieldId = typeof(People).GetField("Id");
MemberExperssion exp = Experssion.Field(p,fieldId);
MethodInfo toString = Typeof(People).GetField("Id");
var toString = Experssion.Call(exp ,toString ,Array.Empty<Expression>());
MethodInfo equals = typeof(string).GetMethod("Equals",new Type[] {typeof(string)});
Experssion expressionContant = Expression.Contant("5");var equalExp = Expression.Call(toStringExp,equals, new Expression[]{expressionContant
});//equalExp == p.Id.Tostring().Equals("5")Expression<Func<People, bool>> expression = Expression.Lambda<Func<People, bool>>(equalExp, new ParamterExpression[]{p
});//expression == p => p.Id.Tostring().Equals("5")bool bResult = experssion.Compile().Invoke(new People(){Id = 5});//experssion.Compile()定义了一个委托
{}
7.1 表达式目录树是如何拆解成Sql语句被数据库识别
//ExpressionVisitor//带有Visitor的是访问者模式//ExpressionVisitor.Visit()方法是访问表达式目录树的入口:1、首先会判断是什么类型的表达式目录(And、GreaterThan);2、会自动的调用到更加专业的方法中去进一步访问(ExpressionVisitor.多个具体分方法名());
//ExpressionVisitor.多个具体分方法名()//转换成SQL能识别的;例1:将 C#中的 && 转换成 SQL 中的 and;例2:程序中需要只允许减号-的存在,一般这个时候,需要我们去重写微软封装的方法,通过人为的判断,将+号全部改成减-号;我们可以自己定义一个类,来继承自ExpressionVisitor,然后重写封装的方法;
public static main()
{Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;//定义了一个表达式目录树;OperationsVisitor visitor = new OperationsVisitor();Expression expNew = visitor.Modify(exp);
}public class OperationsVisitor : ExpressionVisitor
{public Expression Modify(Expression expression){return this.Visitor(expression);//调用ExpressionVisitor中的Visitor方法;访问表达式目录树的入口}//1.重写ExpressionVisitor中的VisitBinary方法protected override Expression VisitBinary(ExpressionBinary b)//VisitBinary对应ExpressionBinary{if(b.NodeType == ExpressionType.Add){Expression left = this.Visit(b.Left);//递归进入VisitBinary()Expression right = this.Visit(b.Right);return Expression.Subtract(left, right);}return base.VisitBinary(b);}//同理可以重写多个基类中的方法
}
8.async / await 语法糖
async 修饰方法名称的;
主线程遇到await A后就返回了,await A后面的内容由新线程执行;await A的线程遇到await B时又被返回了(await A被放回线程池,表示A活干完了),await B后面的内容被await B执行;总结:线程遇到await A后就返回
程序中要么不用await,要么从头用到尾;
//当程序中没有async时,想用async时;
await Task.CompletedTask();//官方框架推荐
await Task.Delay();
结果:可以增加吞吐量,提升性能;
但是:不是所有的程序都适用async/await;如下
await / async能并发吗?一定能提升性能吗?意义何在?
单个方法内不能并发,线程在遇到await 时就返回了,后续内容由新线程执行,属于串行执行;
不能提升性能,性能会低于同步调用,比如:对于同一个方法,单次处理,是没有性能提升的,只会增加负荷;
意义:1、在多请求并发处理,且资源有限的时候,能增加吞吐量(增加单位时间处理的请求)。
举例:一个孩子进食时间为一小时,**1个老师喂4个孩子(await并行)的时候与和一个老师喂一个孩子(普通串行)**进行比较;await并行时,老师在喂第一个孩子的时候,剩下三个孩子自己进食,然后依次喂食,最终花费时间会小于4小时;普通串行时,老师一个一个喂,最终还是花费4小时;
ILSpy 5.x反编译dll文件,查看源码
ILSpy 中存在状态机,用变量_State表示,状态机模式:一个对象根据不同的状态会有不同的行为(对象只有一个,类似于红绿灯)
Task和await的区别
举例:读本地文件;
Task方式–启动10个线程,分别等着读取文件 ,线程一直处于等待状态,cpu一直被消耗;
await方式–实际上看不到线程,调用系统封装的异步API,发起请求,线程就回去了–由硬件自行读取,读完之后发信号,然后线程池再安排线程去处理;
类似例子:还有调用webApi,WCF,远程数据库链接,连接第三方、读缓存;(都值得使用async)
类似于纯CPU计算类型的(不值得使用async,反而浪费线程资源)
9.反射
9.1反射的原理、反射的使用
9.1.1反射的原理
什么是反射?
答:Reflection命名空间,是微软的一个帮助类库,可以读取metadate元数据,可以使用对应metadate元数据中的方法;
高级语言到机器识别过程:C#高级语言 --> 编译器编译 --> DLL/EXE(又包含了metadate元数据(类似于清单,只能看见方法名)、IL(中间语言)) --> CLR/JIT --> 机器码010101
9.1.2反射的使用
在IOC中的实现;
//基础类
public class SqlServerHelper : IDBHelper
{public SqlServerHelper(){}public void Query(){//查询内容}
}public Interface IDBHelper()
{public void Query();
}
public static IDBHelper CreateInstance()
{//1.动态加载DLLAssembly assembly = Assembly.Load("dll名称");//LoadFrom()//2.获取类型Type type = assembly.GetType("dll名称.对应的Helper类A");//Helper类A继承IDBHelper//3.创建对象object odbHelper = Activator.CreateInstance(type);//4.类型转换成对应的HelperIDBHelper dbHelper = odbHelper.IDBHelper;//5.调用方法dbHelper.Query();
}
9.1.3反射的好处和局限
为什么要这么做呢?
1.这样可以断开对细节的依赖(不用添加命名空间引用)
2.实现程序可配置:公司来了技术经理,要求更换数据库为Mysql,按照普通方式写的话,需要改代码,重新编译,重新发布等麻烦步骤;通过反射的话,只需要修改appsetting.json中的即可,不需要重新改代码,重新编译等麻烦步骤,实现了程序可配置;(前提两个数据库的Helper类已经存在了,减少了改代码,编译,发布等步骤)
3.增加了程序扩展性(如果公司只有sqlserver和mysqlhelper,没有orcale数据库的helper类,现在又需要使用orcale数据库,我们最终只需要添加新的orcale project + 改配置文件即可;)
4.实现的就是IOC控制反转的核心思想;
9.1.3.2 局限
9.1.4封装
9.1.2中的程序看起来需要很多代码,所以我们要进行封装一下就好;
public class SimpleCreateInstance()
{public Static IDBHelper CreateOnstance(){//(1)基本封装代码Assembly assembly = Assembly.Load("dll名称A");//LoadFrom()Type type = assembly.GetType("dll名称A.对应的Helper类A");//这两个步骤可通过配置文件进行优化,如下object odbHelper = Activator.CreateInstance(Type);IDBHelper dbHelper = odbHelper.IDBHelper;return dbHelper;//(2)优化封装代码//core中在appsetting.json通过key value进行配置;//配置如:"dbHelperRefliction":"dll名称A.对应的Helper类A, dll名称A.dll"string TypeName = CustomConfigManager.GetConfig("dbHelperRefliction").Split(',')[0]; //dll名称A.对应的Helper类Astring DllName = CustomConfigManager.GetConfig("dbHelperRefliction").Split(',')[1]; //dll名称AAssembly assembly = Assembly.Load(DllName);//LoadFrom()Type type = assembly.GetType(TypeName);//这两个步骤可通过配置文件进行优化,如下object odbHelper = Activator.CreateInstance(Type);IDBHelper dbHelper = odbHelper.IDBHelper;return dbHelper;}
}
public static main()
{IDBHelper idbHelper = SimpleCreateInstance.CreateInstance();idbHelper.Query();
}
9.2反射在项目的应用
MVC中的实现:
localhost:8080/Home/Index
为什么浏览器访问这个路径,就可以进入到Action中去呢?最终就是调用Action。
{//----------------------------//(1)无参公共方法Console.WriteLine("反射调用普通方法");//1.动态加载dllAssembly assembly = Assembly.LoadForm(@"xxx.dll");//2.获取类型Type type = assembly.GetType(@"xxx.dll.类名");//3.创建对象object oTest = Activator.CreateInstance(type);//方法如何调用呢?//4.获取方法MethodInfo show1 = type.GetMethod("show1");//show1为自定义的无参方法;//5.利用Invoke去调用方法show1.Inoke(oTest,new object[0]);//Inoke需要传输两个参数,所以我们就创建了一个空的对象;new object[0]写法是新的写法;//结果:成功调用//-----------------------------//(2)带参数的公共方法MethodInfo show2 = type.GetMethod("show2");//show2为自定义的有参方法;show2(int id);show2.Inoke(oTest,new object[]{123});//成功show2.Inoke(oTest,new object[]{"ZiFuChuan"});//失败;注意:在传递参数的时候,一定要注意,必须要和方法的参数类型一致;//-------------------------------//(3)带参数的公共重载方法//show3(int id, string name);//show3(string name, int id);//show3(int id);//MethodInfo show3 = type.GetMethod("show3");// 这种方式是有错误的,因为不知道是哪个具体的方法;MethodInfo show3 = type.GetMethod("show3", new Type[] {typeof(int), typeof(string)});//这样就获取到了show3(int id, string name)方法show3.Inoke(oTest,new object[]{123, "ZiFuChuan"});//成功//---------------------//(4)带参数的**私有**方法//show4为自定义的有参方法;show4(string name);//MethodInfo show4 = type.GetMethod("show4");//因为是private,所以这样获取不到的MethodInfo show4 = type.GetMethod("show4", BindingFlags.NonPublic | BindingFlags.Instance);//传递枚举进去NonPublic,就可以找得到private方法了;show4.Inoke(oTest,new object[]{"ZiFuChuan"});//---------------------//(5)静态方法MethodInfo show5 = type.GetMethod("show5");//4.获取方法show5.Inoke(oTest,new object[]{"ZiFuChuan"});//成功show5.Inoke(null,new object[]{"ZiFuChuan"});//成功。静态方法的调用其实不需要具体的实例,只需要“类型.静态方法名”即可,所以这里不传递实例也是可以的。调用静态方法的基础知识;//---------------------//(6)普通类的泛型方法//show6<T, W, X>(T t, W w, X x)//普通方法调用方式:对象.Show6<int, string, DateTime>(123, "ZiFuChuan", DateTime.Now);//普通方法调用方式:对象.Show6(123, "ZiFuChuan", DateTime.Now);这种方法之所以成功是因为语法糖来确定的;MethodInfo show6 = type.GetMethod("show6");//4.获取泛型方法,成功//show6.Inoke(oTest,new object[]{123, "ZiFuChuan", DateTime.Now});//失败MethodInfo showNew6 = show6.MakeGenericMethod(new Type[]{typeof(int), typeof(string), typeof(DateTime)});//MakeGenericMethod指定类型showNew6.Inoke(oTest,new object[]{123, "ZiFuChuan", DateTime.Now});//---------------------//(7)泛型类的泛型方法1//show7<T, W, X>(T t, W w, X x);;public class A<T, W, X>Assembly assembly = Assembly.LoadForm(@"xxx.dll");//这里class为A//Type type = assembly.GetType(@"xxx.dll.A");//结果:获取不到Type type = assembly.GetType(@"xxx.dll.A`3");//结果:成功;`3表示泛型,参数有3个;//object oTest = Activator.CreateInstance(type);//失败,因为通过普通方法 A obj = new A();是不允许的;需要指定类中的泛型;Type typeNew = type.MakeGenericType(typeof(int), typeof(string), typeof(DateTime));//MakeGenericType指定类型object oTest = Activator.CreateInstance(typeNew);//成功MethodInfo show7 = typeNew.GetMethod("show7");//成功,因为是基于类的泛型;这里就不需要再次指定类型了show7.Inoke(oTest,new object[]{123, "ZiFuChuan", DateTime.Now});//---------------------//(8)泛型类的泛型方法2//show8<T, W, X>(T t, W w, X x);;public class A<T>//+++++++注意这里类的泛型+++++++Assembly assembly = Assembly.LoadForm(@"xxx.dll");//这里class为AType type = assembly.GetType(@"xxx.dll.A`1");//结果:成功;`1表示泛型,参数有1个;Type typeNew = type.MakeGenericType(typeof(int), typeof(string), typeof(DateTime));//MakeGenericType指定类型object oTest = Activator.CreateInstance(typeNew);//成功MethodInfo show8 = typeNew.GetMethod("show8");//成功的,但是剩下2个泛型类未指定;需要进行指定show8New = show8.MakeGenericMethod(new Type[]{typeof(int), typeof(DateTime)});//MakeGenericMethod指定类型,方法中剩下的泛型按顺序指定;show7.Inoke(oTest,new object[]{"ZiFuChuan", 123, DateTime.Now});//成功}
总:所以实际上在MVC请求网站的时候 ,会一步一步的解析出域名、端口、ControllerName、方法Index;实际上是通过反射的方式定位到具体的位置;
9.3反射封装框架,在线手写ORM框架
9.3.1通过反射操作属性或字段
{///1.普通方式People people = new People(){Id = 123,Name = "zifucahun",Age = 25};Console.WriteLine($"{people.Id}");//取值///2.反射的实现,如下Type type = typeof(People);object oPeople = Activator.CreateInstance(type);//创建实例//赋值foreach(var prop in type.GetProperties){if(prop.Name.Equals("Id")){prop.SetValue(oPeople, 123);//赋值}}//取值foreach(var field in type.GetFields()){Console.WriteLine($"oPeople.{field.Name} = {field.GetValue(oPeople)}");//遍历取值}//反射的好处//1.通过普通方式无论是赋值还是取值都需要需改代码(当增加、删除、更新一个字段时)//2.反射取值的时候,就不需要改代码,但是赋值的还是需要修改的; }
9.3.2 使用反射在ORM框架的实现
public class main
{SqlServerHelper sqlServerHelper = new SqlServerHelper();//Student 和Teacher 两个Model对应数据库表;Student s1 = sqlServerHelper.Query(1);Teacher t1 = sqlServerHelper.Query(1);
}public class SqlServerHelper
{//ORM,面向对象思想。本质:通过对类的操作,实现对数据库的操作(类与数据库的字段和类型必须一致)Public T Query<T>(int id) //Public T Query<T>(string sqlstr){//1.数据库连接字符串string conn ="Data Souce=主机名称; Database=数据库名称;User ID=sa;Password=数据库登录密码; MultipleActiveResultSets=True;";//2.准备SQl语句string sql = "";//+++++SQL语句可以拆分外部,灵活性更大;++++Type type = typeof(T);//获取泛型T的类名称;//泛型T类对应数据库表;object oObj = Activator.CreateInstance(type);//创建泛型T类对应的实例oObj //3.连接数据库Using(SqlConnection connection = new SqlConnection(conn)){connection.Open();SqlCommand sqlCommand = new SqlCommend(sql, connection);//将SQL语句连接数据库;SqlDataReader reader = sqlCommand.ExecuteReader();//准备执行数据读取reader.Read();//读取数据//下面使用反射的方式;foreach(var prop in type.GetProperties())//遍历T的所有属性{//给泛型T的实例对象oObj设置属性值,属性值通过从reader索引获取;value = reader[属性名称];prop.SetValue(oObj,reader[prop.Name]);//注意写法}}return (T)oObj;// return oObj as T;//注意这两种写法的区别}//如果只想查询部分字段,可以通过 “特性” 进行过滤;
}