开始
-
C#入门书籍
- C#图解教程
- C#入门经典
-
最开始 .Net 代表了 net core , .net Framework,但现在默认 .NET 就是指的.NET Core
-
.Net Standard :是规范,.net core , .net Framework等.net技术需要实现.Net Standard中定义的类、方法
- 可以理解为.Net Standard规定了公共的类,而其他的例如.net core , .net Freamwork在其基础上又添加了自己特有的类
- .Net Standard只是标准,不提供实现
1. NuGet
- NuGet官网
- 当不知道包名的时候,可以在官网通过需求关键字搜索相应的包
- 官网里面有些包没有写依赖 Dependencies,一般就是依赖于.net Framework (一般是很老的包没有依赖)
- 版本号中alpha、Beta都是非正式版的标识(不稳定)
1.1 通过命令行方式使用NuGet
-
Package Manager方式
-
在VS的程序包管理器控制台添加包
-
-
-
在默认项目选择将包装到指定项目
-
命令不带 -Version 则默认安装最新稳定版
-
-
更新包
Update-package+包名
-
删除包
UnInstall-Package+包名
(其实就是在原命令上加Un) -
双击项目名可看到安装的包
<ItemGroup> <PackageReference Include="Zack.EFCore.Batch" Version="1.6.3" /> </ItemGroup>
-
如果界面没有程序包管理器,在视图中去找
-
-
.Net CLI方式
- 直接在CMD命令行进行包的添加(需要进入项目路径中)
1.2 通过图形界面使用
- NuGet包管理器
- 在批量删除、更新包的时候图形界面更方便
1.3 内部部署Nuget包
- 公司内部可以搭建自己的Nuget服务器,仅限内部使用
2. 异步编程:async、await
2.1 开始
- 简单理解异步:“不等”
- 异步编程并不能加快单个请求的处理速度,但是可以让web服务器处理更多的请求(提高系统并发量)
- 异步不是多线程
- async、await不等于多线程,但是可以简化多线程程序的开发
2.2 async、await基本使用
- 异步方法定义:用async关键字修饰的方法
-
异步方法的返回值一般是
Task<T>
,T是真正的返回值类型,例如Task<int>
- 异步方法的命名常以Async结尾
-
即使方法没有返回值,也最好将返回值写为Task,不要写成Void
- 写成void容易出现问题
-
调用泛型方法时,一般会在该方法前面加上await关键字
-
这样拿到的返回值就是泛型指定的T类型
int i=await FuncAsync(); //await使得Task<i>中的i被解析出来了
-
-
如果一个方法中有await,则这个方法也必须被async关键字修饰
- 如果方法被async关键字修饰,在内部调用异步方法不写await也可以执行,编译器只是会警告
-
2.3 编写异步方法
-
对于同步、异步都实现的方法,推荐调用异步方法
-
对于在一个同步方法中,不得不调用异步方法时,可以使用Result或者Wait()
-
对于一个有返回值
Task<T>
的异步方法,想要不使用await拿到T类型的值(也就是同步调用的方式调用这个异步方法),可以通过Task的Result属性实现-
使用方式就是在异步方法后面 .Result
-
int i=FuncAsync().Result; //上面的语句等同于 Task<int> t=FuncAsync(); int i=t.Result;
-
-
-
对于一个没有返回值异步方法,想要不使用await调用这个异步方法(也就是同步调用的方式调用这个异步方法),可以通过Task的wait方法实现
-
例如在一个只能是同步的方法FunA()中调用这个异步方法,就需要将这个异步方法调用wait方法
-
int FuncA() { FuncAsync().Wait(); }
-
-
-
注意:如果调Wait或者Result,容易出现死锁的情况(表现就是程序卡住了)
-
-
在lamda表达式中调用异步方法(例如委托),直接调用会报错,可以在lamda表达式前加上async关键字,将lamda表达式修饰为异步的,如:
fun(await ()=>{ await FuncAsync(); });
2.4 async、await原理
- async、await是“语法糖”,最终会编译成状态机调用
- async的方法会被C#编译器编译成一个类,会主要根据await调用进行切分为多个状态,对async方法的调用会拆分为对MoveNext的调用
- 用await看似是”等待“,经过编译后,其实没有等待
- async背后线程切换
- await调用的等待期间, .Net会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出来一个线程执行后面的代码(存疑:和杨中科讲的不同,我自己测试了一下,执行异步方法的线程已经是新的线程,而这个新线程在执行完异步方法后,会接着执行后面的代码)
- 不能保证再次使用的线程与之前线程是同一个线程
- 但是如果异步方法执行时间很短,很有可能前后都是同一个线程
- await调用的等待期间, .Net会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出来一个线程执行后面的代码(存疑:和杨中科讲的不同,我自己测试了一下,执行异步方法的线程已经是新的线程,而这个新线程在执行完异步方法后,会接着执行后面的代码)
2.5 异步方法和多线程
- 异步不等于多线程
- 异步方法的代码并不会自动在新线程中执行,除非把手动代码放到新线程中执行
- 例如使用 Task.Run(()=>{ … })来执行就可能使用一个新的线程
2.6 不使用async和await实现异步方法调用
-
使用情况:在一个方法简单调用(没有复杂逻辑,而是转发调用一下)异步方法的时候可以省去写async和await
-
如果本方法中不需要对返回结果做处理直接将Task返回给由需要结果值的上级调用者自己await,不做赚差价的中间商,提高效率
-
对于方法A中调用异步方法B的传统写法
public async Task<int> Aasync(){ return await Basync(); }
-
对以上方法进行改造,以下代码是实现了同样的功能
public Task<int> Aasync(){ return Basync(); }
-
分析
- 传统方式下await Basync()先是将int值取出,最后又封装成
Task<int>
再返回 - 简写之后,不用await就可以直接返回
Task<int>
,这样就只是一个普通的方法调用(且这样运行效率更高,不会”拆了再装“)。这样就不会等了,程序执行到异步方法后会直接执行下一行代码。
- 传统方式下await Basync()先是将int值取出,最后又封装成
-
简写方式不适用的情况
-
不是简单调用异步方法,而是对被调用异步方法的返回值处理后再返回,简写反而会更加麻烦
public async Task<int> Aasync(){ int res=awiat Basync(); return res+1; } //res被处理过,更适合用async、await写法
-
-
总结:返回值为Task的不一定都要标注async,标注了async只是让我们可以更加方便的await而已
-
2.7 Task.Delay()
- 如果想在异步方法中暂停一段时间,不要用Thread.Sleep(),因为它会阻塞调用线程,而要用await Task.Delay()
2.8 CancellationToken
-
CancellationToken使用场景:当需要提前终止任务时(例如,请求超时或用户取消请求),利用CancellationToken可获得提前终止执行的信号。
- 例如下载资源时,用户跳转的其他网页,但是服务器仍会执行任务,用CancellationToken便可以识别下载任务是否取消,这样可以节约服务器资源
-
CancellationToken是结构体,包含如下成员
-
None :空
-
bool isCancellationRequested 是否取消请求
//在请求方法中,用于判断 async Task Dowmload() { 请求代码。。。 if(CancellationToken.isCancellationRequested ){ 执行请求被取消后的代码 } }
-
Register(Action callback) (不常用) 注册取消监听
-
ThrowIfCancellationRequested() 如果任务被取消,执行到这句话就会抛异常
//在请求方法中,如果取消了请求,就会抛出异常(需要处理异常,下面代码为了直观不处理了) async Task Dowmload() { 请求代码。。。 CancellationToken.ThrowIfCancellationRequested(); }
-
-
调用CancellationToken
-
一般通过CancellationTokenSource来创建CancellationToken
CancellationTokenSource cts=new CancellationTokenSource(); //设置超时3s取消请求 cts.CancelAfter(3000); //获取CancellationToken对象 CancellationToken ct=CancellationTokenSource.Token; //在方法中传入CancellationToken对象,一旦方法执行超过3s,就会取消执行 Dowmload(ct)
-
CancellationTokenSource包含的重要成员有:
- CancelAfter() 超时后发出取消信号
- Cancel() 发出取消信号
- CancellationToken Token
-
-
对与系统写好的方法,如果有CancellationToken 参数,那么调用这个方式时尽量把CancellationToken 传进去,“能转发CancellationToken 就转发”
- 如果是自己写CancellationToken ,识别到任务取消后,我们还得去取消执行
- 如果是调用系统的方法,一般都是识别、执行都写好的
-
对于asp.net core的action,都可以传CancellationToken 作为参数,asp.net core框架会自动赋值
2.9 WhenAll()
- WhenAll(): 用于等待多个Task执行结束,但不关心顺序
- WhenAny(): 任何一个Task完成,这个Task就完成了
2.10 异步的其他知识点
-
接口中的异步方法不需要写 async
-
异步与yield
-
yield return :可以实现数据处理流水话,提升性能
-
//如代码 fun(){ List<string> list=new List<string>(); list.add("abc"); list.add("def"); list.add("ghi"); return list; } //用yield return 可以实现一样的效果 fun(){ yield return "abc"; yield return "def"; yield return "ghi"; }
-
在C#8.0之前 async方法中不能用和yield,而从8.0开始,将返回值声明为IAsyncEnumerable即可(不写Task)
async IAsyncEnumerable<string> test() { yield return "abc"; yield return "def"; yield return "ghi"; }
-