多线程5:使用C# 6.0
1、异步函数
(1) 创建异步函数的方法:
- 使用async关键字标注一个方法;
- 函数要返回Task或Task<T>类型;
- 使用async标注的方法内部要使用await操作符。
TPL为含有async关键字的方法添加了async属性或时间访问方法和构造函数。
异步函数不用返回Task或Task<T>类型的特例是使用顶层UI事件处理的时候,可以使用async void标注异步函数。
(2) await操作符可以获取异步操作的结果,只能在async标注的异步方法内使用,异步方法至少要包含一个await操作符。
(3) 执行完await调用的代码后异步方法会立即返回,并将工作者线程放回线程池中,进行异步等待;
异步操作完成后,继续使用线程池中的工作者线程运行剩余的异步方法。
(4) 异步函数的程序控制流是线性的,但它的执行是异步的,连续的await操作符也是顺序执行的。
(5) 调用异步函数会有显著的性能损失,通常的函数调用比使用async关键字时要快上40~50倍。
2、使用await操作符获取异步任务结果
使用await后,C#立即创建了一个任务,它有一个后续操作任务(包含await操作符后面的所有剩余代码),将该任务返回到主方法中并等待其完成。
和await操作符关联的任务也处理了异常的传播,将await操作符放在try/catch块内即可捕获异步操作中的异常。
在Windows GUI或ASP.NET之类的环境中不推荐使用Task.Wait和Task.Result,因为这可能导致死锁。
3、在lambda表达式中使用await操作符
由于任何lambda表达式的类型都不能通过它自身来判断,所以独立的lambda表达式定义需要向C#编译器显式指定其类型。
4、对连续的异步任务使用await操作符
连续执行2个await语句,会按照await调用的顺序依次执行,每个await语句执行完毕后及返回,等待异步函数调用执行完毕。
5、对并行执行的异步任务使用await操作符
Task.WhenAll方法会创建一个任务,该任务在所有底层任务完成后开始执行。
Task.Delay方法使用一个计时器进行等待,其过程为:
- 从线程池中获取工作者线程,它将等待Task.Delay方法返回结果;
- Task.Delay方法启动计时器并指定一块代码,该代码会在计时器时间到后被调用;
- 之后立即将工作者线程返回到线程池中;
- 当计时器事件运行时,从线程池中获取一个可用的工作者线程(可能就是之前使用的线程),并运行计时器提供的代码。
6、处理异步操作中的异常
(1) await操作符应用于一个异步操作时,使用try/catch块可获取异步操作的异常。
(2) await操作符应用于Task.WhenAll时,使用try/catch块仅可获取聚合异常对象中的第1个异常;
通过Task3.Exception.Flatten()方法可以将层级异常放入一个列表中,通过InnerExceptions属性可以获取所有异常。
7、避免使用捕获的同步上下文
默认情况下,await操作符会尝试捕获同步上下文,并在其中执行代码。
在UI中使用await不会发生死锁现象,因为等待结果时不会阻塞UI线程。
使用如下语句可以避免在捕获的同步上下文中进行异步等待:
await task.ConfigureAwait(continueOnCapturedContext: false);
在UI线程中使用await会使得异步操作向UI线程中放入成百上千个后续操作任务,这会使用它的消息循环来异步执行这些任务。
8、使用async void方法
使用async void方法,异常处理方法将被放置到当前的同步上下文中,一般为线程池中。
线程池中未被处理的异常会终结整个进程。
使用AppDomain.UnhandledException事件可以拦截未被处理的异常,但不能从拦截的地方恢复进程。
强烈建议只在UI事件处理器中使用async void方法。
9、自定义awaitable类型
await表达式的任务被要求是awaitable。如果一个表达式t满足下面任意一条则认为是awaitable的:
- t是动态编译时的类型
- t有一个名为GetAwaiter的可访问的实例或扩展方法。该方法没有参数和类型参数,并且返回值类型A满足以下所有条件:
- A实现了INotifyCompletion接口;
- A有一个可访问的、可读的类型为bool的实例属性IsCompleted;
- A有一个名为GetResult的可访问的实例方法,该方法没有任何参数和实例参数。
await表达式的执行方式:
- 如果IsCompleted属性返回true,则只需同步调用GetResult方法;
- 如果IsCompleted属性返回false,则调用INotifyCompletion接口的OnCompleted方法。
是由具有无法使用Task的坚实理由,才可以自定义awaitable类型,并且需要真的知道你在做什么。
10、对动态类型使用await
使用dynamic关键字组合ExpandoObject允许我们自定义对象并通过分配相应的值来添加属性和方法。
事实上dynamic是一个字典类型的集合,键类型是string,值类型是object。
ExpandoObject的工作方式:
当给属性分配值时,ExpandoObject创建了一个字典条目,键是属性名,值是赋予的任何值。
当尝试获取属性值时,会在字典中查找并提供存储在相应字典条目中的值。
如果该值是Action或Func类型,实际上存储了一个委托,可以当作方法使用。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器