进程是正在执行中的程序,进程中执行的每个任务即为一个线程;
线程属于进程;线程离不开委托;
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;//设置后台线程;当进程关闭,线程随之取消;
按照顺序一个一个执行
同时工作 同时工作 同时工作 同时工作 同时工作 同时工作
1.效率高
2.消耗资源高
3.各线程执行顺序 难以控制
如何控制 多线程 的执行顺序 --> 回调 (多线程实例:线程 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; }); }
线程池中有很多创建好的线程,用的时候拿出来用,用完了或者不用了,就放回线程池;
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的默认值为false ThreadPool.QueueUserWorkItem(o => { Console.WriteLine("线程o") mre.Set(); });//从线程池中获取线程 //线程等待 mre.WaitOne();//等待上一个线程先执行; } Console.WriteLine("线程1-1"); }
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的时候,创建了多个线程去不同的地方同时进行查找,当有一个完成查找之后,就可直接返回数据,页面进行相应;
表达式目录树 实际上是一个数据结构(二叉树)。
为什么使用表达式目录树?
答:表达式目录树 – 动态的
初级阶段:数据库查询的时候,都是拼接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); } //同理可以重写多个基类中的方法 }
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,反而浪费线程资源)
什么是反射?
答:Reflection命名空间,是微软的一个帮助类库,可以读取metadate元数据,可以使用对应metadate元数据中的方法;
高级语言到机器识别过程:C#高级语言 --> 编译器编译 --> DLL/EXE(又包含了metadate元数据(类似于清单,只能看见方法名)、IL(中间语言)) --> CLR/JIT --> 机器码010101
在IOC中的实现;
//基础类 public class SqlServerHelper : IDBHelper { public SqlServerHelper() { } public void Query() { //查询内容 } } public Interface IDBHelper() { public void Query(); }
public static IDBHelper CreateInstance() { //1.动态加载DLL Assembly assembly = Assembly.Load("dll名称");//LoadFrom() //2.获取类型 Type type = assembly.GetType("dll名称.对应的Helper类A");//Helper类A继承IDBHelper //3.创建对象 object odbHelper = Activator.CreateInstance(Type); //4.类型转换成对应的Helper IDBHelper dbHelper = odbHelper.IDBHelper; //5.调用方法 dbHelper.Query(); }
为什么要这么做呢?
1.这样可以断开对细节的依赖(不用添加命名空间引用)
2.实现程序可配置:公司来了技术经理,要求更换数据库为Mysql,按照普通方式写的话,需要改代码,重新编译,重新发布等麻烦步骤;通过反射的话,只需要修改appsetting.json中的即可,不需要重新改代码,重新编译等麻烦步骤,实现了程序可配置;(前提两个数据库的Helper类已经存在了,减少了改代码,编译,发布等步骤)
3.增加了程序扩展性(如果公司只有sqlserver和mysqlhelper,没有orcale数据库的helper类,现在又需要使用orcale数据库,我们最终只需要添加新的orcale project + 改配置文件即可;)
4.实现的就是IOC控制反转的核心思想;
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类A string DllName = CustomConfigManager.GetConfig("dbHelperRefliction").Split(',')[1]; //dll名称A Assembly 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(); }
MVC中的实现:
localhost:8080/Home/Index
为什么浏览器访问这个路径,就可以进入到Action中去呢?最终就是调用Action。
{ //---------------------------- //(1)无参公共方法 Console.WriteLine("反射调用普通方法"); //1.动态加载dll Assembly 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为A Type 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.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;//注意这两种写法的区别 } //如果只想查询部分字段,可以通过 “特性” 进行过滤; }