深入async/await知多少

      .net的async/await功能相信对很多人来说并不陌生了,有人感觉这功能很好,但也有人说这功能不好容易产生一些莫名其妙的死锁;有人说这些异步功能也有人说这是同步功能。其实在使用async/await的有多少人真的了解它们呢?接下来详细地讲述它们和在什么情况下需要注意的细节。

为什么需要它

      如果你对async/await的需求不明显,那只能说明你平时很少写异步逻辑(当你很少接触传统异常逻辑编写的情况下,最好也找些相关功能用一下先也了解一下传统异常调整用情况)。在传统Begin/End的异步模式中所有结果都是由回调函数来处理,当一个逻辑有多层异步代码时那整个业务逻辑代码混合着各大和业务无关的回调函数中游走,这会让代码变更难以维护;当有了async/await后这种方式得到了解脱,因为你再不需 在不同Callback函数了维护一简单的逻辑了;取而代之的就是我们平时普通方法调用一样,只是在调用间加个await关键字

工作原理

      async/await简单来说只是一个语法粮,它只是告诉编译器要把这些代码编译成一个异步状态机。它的基础工作接口如下:

    public interface INotifyCompletion{void OnCompleted(Action continuation);}

当然只有这个接口还不足以让编译去构建一个Awaiter,还要扩展两方法

    public interface IAwaiterObject : INotifyCompletion{IAwaiterObject GetAwaiter();object GetResult();}

      IAwaiterObject必须继承INotifyCompletion接口,这样就可以构成了一个最简单Awaiter规则,只要方法返回IAwaiterObject即可以使用await关键字进行异步处理。那编译器是怎样生成其对应的异步代码的呢?可以通过以下代码你就能更好地理解。

var item = await GetData();
Console.Write(item)

以上的await代码你可以解决为

action=>Console.Write(item);
if(awaiter(getdata(),action).completed())action();

编译会包装一个状态机,当getdata是同步完成时接下来就调用action方法,如果getdata是异步完成由其内异步回调方法来调用action方法.

Task是什么?

      async/await如果每一步都要自己的封装那这个功能使用门槛就非常高了,为也让这功能更好地使用所以.net提供了一个async/await的基础实现,那就是Task.Task提供一系列完善的功能主要包括:自有的awaiter线程调度器,wait同步等待行主和TaskCompletionSource<T>等一系列简化async/await使用自定义扩展需求功能。

同步还是异步?

      async/await是一个异步处理模型,但并不能说明所有的async/await都是异步处理;具体要看Awaiter状态机是由谁触发的,当上层方法逻辑是同步或IO同步完成的情况那await后面的代码则由同当前线程触发执行,如果上层方法是异步完成的情况下则由对应相关异步完成的线程调用;所以async/await也有些情况是同步完成的,只是这种情况在IO处理上并不多见.

await后面代码由什么线程执行?

      有部分人认为只要使用async/await那就肯定是多线程处理,其实这并不完全正确,前面提到了即使IO也有同步完成的时候,所以是存在相关代码都由当前线程来处理的.async/await最基础就是状态机异步回调,所以await后面的代码肯定是由回调线程来执行,如果起始的awaiter是Task.Run触发那后面的代码则于Task内部线程池来处理;在调用IO的时候其实相样后期代码则有IO回调线程来完成。

      其实在实际运行过程中,方法中一连串的await代码是由一个或多个线程来共同完成;为什么会存在这情况呢,主要还是和相关awaiter下层实现有关系。如果由同一个线程执行那说明相关方法都同步完成了。那多个线程完成的是什么情况呢?主要还是方法中有多个IO await代码块,而每个IO都是异步完成的这样就会导致后面每个await代码块都是由上一个IO回调线程来处理。

      实际使用是否自己控制?一般基础的实现都是由实现者来管理回调线程,不过自己可以在中间加入一个TaskCompletionSource<T>代理返回就可以控制后续工作线程的运作了,通过TaskCompletionSource<T>后就可以制定自己的线程队列机制来触发Completed了。

改造传统异步方法

      如果有的逻辑还是基于Begin/End方法,但基础方法又不提供async/await怎办呢?虽然可以自己封装一个Awaiter来处理,但简单有效的办法就是使用TaskCompletionSource<T>对象;可以在一个方法并返回对应的Task

public Task<int> Add()
{TaskCompletionSource<int> result = new TaskCompletionSource<int>();//beginreturn result.Task;
}

后面的工作就是在End方法调用相关的方法即可

public void SetCanceled();public void SetException(IEnumerable<Exception> exceptions);public void SetException(Exception exception);public void SetResult(TResult result);public bool TrySetCanceled();public bool TrySetCanceled(CancellationToken cancellationToken);public bool TrySetException(IEnumerable<Exception> exceptions);public bool TrySetException(Exception exception);public bool TrySetResult(TResult result);

说实话TaskCompletionSource<T>这个对象设计成泛型还真不好用,毕竟在一引起底层设计中用时候很难固定这个T的,特别是在反射调用await的情况下,在这里分享一个扩展支持object设置的TaskCompletionSource<T>类;

    class AnyCompletionSource<T> : TaskCompletionSource<T>, IAnyCompletionSource, IInvokeTimeOut{public AnyCompletionSource(){ID = System.Threading.Interlocked.Increment(ref mID);}public long TimeOutElapsed { get; set; } = 10000;static long mID;public long ID { get; private set; }private int mCompleted = 0;public Action<IAnyCompletionSource> Completed { get; set; }public Action<IAnyCompletionSource> TimeOut { get; set; }public void Success(object data){if (System.Threading.Interlocked.CompareExchange(ref mCompleted, 1, 0) == 0){TrySetResult((T)data);OnCompleted();}}public void InvokeTimeOut(){if (TimeOut != null){TimeOut(this);}else{if (System.Threading.Interlocked.CompareExchange(ref mCompleted, 1, 0) == 0){TrySetException(new TimeoutException($"{this.GetType()} process time out!"));}}}private void OnCompleted(){try{Completed?.Invoke(this);}catch{ }finally{}}public void Error(Exception error){if (System.Threading.Interlocked.CompareExchange(ref mCompleted, 1, 0) == 0){TrySetException(error);OnCompleted();}}public Task GetTask(){return this.Task;}public async void Wait<Result>(Task<Result> task, Action<IAnyCompletionSource, Task<Result>> action){try{await task;action(this, task);}catch (Exception e_){Error(e_);}}public async void Wait<Result>(Task<Result> task){try{await task;Success(task.Result);}catch (Exception e_){Error(e_);}}public async void Wait(Task task, Action<IAnyCompletionSource> action){try{await task;action(this);}catch (Exception e_){Error(e_);}}}

如何在反射方法中使用await?

    相信比较少的朋友会这样用,实际上做基础方法代理的时候是需碰到的在这里也简单地说一下。由于Method.Invokd返回的是object,所以无法针对返回值为object的方法进行await的;其实await并不是针对方法行为的,而是针对方法的返回值,所以简单地转换一下对象再await即可

var result = handler.MethodInfo.Invoke(obj, arg);
if (result is Task task)await task;

为何有假死现象?

      有些人说使用async/await程序容易出现莫名其妙的假死现像,其实这种情况的出现主要是使用了Task.wait有关系;主要原因是使用了没有指定超时的wait行为,假设Task.wait是等待下一个awaiter状态通知,但这个时候又调用了Task.wait等待,结果导致当前线程回归到一下状态执行环节结果导致死现像。

      以下针对socket的接收为例:receive->awaiter->wait 以上行为的wait会导致线无法回归到下一次begin receive,既然数据无法继续接收那接下来的状态等待自然就无法得到通知了,这就会造了常见的假死现像!

使用原则

      所以在使用async/await的时候最好不要混合同步等待方法,普通开发者最好遵循要么就全用要么就不用原则(混用风险非常大,如果要用记住加个wait timeout)。如你是一个熟悉的开发者,其awaiter回调线程又是自己控制的那就可以适当的采用,即使这样还是容易进坑的!

        public virtual void OnCompleted(ResultType type, string message){if (System.Threading.Interlocked.CompareExchange(ref mCompletedStatus, 1, 0) == 0){Result.Status = ResultStatus.Completed;Client.TcpClient.DataReceive = null;Client.TcpClient.ClientError = null;Result.ResultType = type;Result.Messge = message;Host?.Push(Client);Completed?.Invoke(this);ResultDispatch.DispatchCenter.Enqueue(this, 3);}}

以上就是一坑代码,由于由于指令用了wait(),结果导致调度中心队列状态回归问题阻塞了。。。后来针对存在wait()需求的指令采用其他方法触发状态机才能解决问题

        public virtual void OnCompleted(ResultType type, string message){if (System.Threading.Interlocked.CompareExchange(ref mCompletedStatus, 1, 0) == 0){Result.Status = ResultStatus.Completed;Client.TcpClient.DataReceive = null;Client.TcpClient.ClientError = null;Result.ResultType = type;Result.Messge = message;Host?.Push(Client);Completed?.Invoke(this);if (Command.GetType() == typeof(SELECT) || Command.GetType() == typeof(AUTH)){Task.Run(() => TaskCompletion());}else{ResultDispatch.DispatchCenter.Enqueue(this, 3);}}}

这些问题说真的非常不好排查,所以没有特别的情况还是遵循这相关原则好!

async void xxx()

      说实话真不建议用这样的方法(不过有时这种方式用起来挺方便的),这种方法会引起状态机断层;断层意味着这方法的异常无法往上抛,就是说调用这些方法的外层方法无法try住这方法异常!如果这些方法内没有进行try那不好意思那就会导致程序直接结束。所以在使用中最好用async Task来代替它,这样可以让状态机再往上层抛,这时候只需要在最顶层try即可。如果是要用那就确认方法内try来处理任何可引发异常的代码。

总结

      以上讲述了async/await的基础原理、使用和一些问题;对于async/await的观点我是强烈建议使用,现有的API方法都已经完全支持了特别是IO接口无一不支持,这样好的异步驱动代码模型没有理由不用的!即使不是异步IO代码也可以通过ValueTask来解决Task带来对象开销过大的问题,所以对于.net的开发者来说应该适应它!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309217.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微软正式发布 gRPC-Web for .NET

今年一月份的时候&#xff0c;微软曾宣布对 gRPC-Web for .NET 的实验性支持微软实验性地对 .NET 支持 gRPC-Web&#xff0c;现在它已正式发布。gRPC 是谷歌开源的高性能、通用 RPC 框架&#xff0c;支持包括 .NET 在内的多种编程语言。它面向移动和基于 HTTP/2 标准设计&#…

Java多线程(review)

文章目录线程状态线程方法线程停止线程休眠——sleep网络延时模拟倒计时与打印当前系统时间线程礼让——yield线程强制执行——Join线程状态线程优先级守护线程不安全案例死锁Lock锁线程状态 新建状态: 使用 new 关键字和 Thread 类或其子类建立一个线程对象后&#xff0c;该线…

利用Azure Functions和k8s构建Serverless计算平台

题记&#xff1a;昨晚在一个技术社区直播分享了“利用Azure Functions和k8s构建Serverless计算平台”这一话题。整个分享分为4个部分&#xff1a;Serverless概念的介绍、Azure Functions的简单介绍、k8s和KEDA的介绍和最后的演示。ServerlessServerless其实包含了两种概念&…

基于 abp vNext 和 .NET Core 开发博客项目 - 终结篇之发布项目

系列文章使用 abp cli 搭建项目给项目瘦身&#xff0c;让它跑起来完善与美化&#xff0c;Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API&#xff0c;包装返回模型再说Swagger&#xff0c;分组、描述、小绿锁接入GitHub&#xff0c;用JWT保护你的API异常处理和…

对 JsonConvert 的认识太肤浅了,终于还是遇到了问题

一&#xff1a;背景1. 讲故事在开始本文之前&#xff0c;真的好想做个问卷调查&#xff0c;到底有多少人和我一样&#xff0c;对 JsonConvert 的认识只局限在 SerializeObject 和 DeserializeObject 这两个方法上(┬&#xff3f;┬), 这样我也好结伴同行&#xff0c;不再孤单落…

.Net微服务实战之DevOps篇

技术只是基础该系列的两篇文章《.Net微服务实战之技术选型篇》和《.Net微服务实战之技术架构分层篇》都是以技术角度出发描述微服务架构的实施。如果技术选型篇叙述的是工具&#xff0c;那么架构分层篇讲的就是技巧&#xff0c;而本篇要讨论的就是原则。一直以来我会给身边向我…

阿里云挑战赛

文章目录第一题题目&#xff1a;题解&#xff1a;第二题题目题解第三题题目题解第四题题目&#xff1a;题解第五题题目题解第六题题目题解第七题题目&#xff1a;题解第八题题目&#xff1a;题解;题解赛后出第一题 题目&#xff1a; 【单选】filter 方法意图过滤传入的订单列…

使用C#编写STM32对接物联网平台IoTSharp发送遥测数据

在之前的文章中&#xff0c; 我们阐述了如何用C#在STM32上写第一个Hello world &#xff0c; 有朋友抱怨国内介绍文章&#xff0c; 都是一个 Hello world &#xff0c; 然后再也没有音讯&#xff0c; 写到这里我想提一下我的初心&#xff0c; 那就是告诉所有人C#无所不能&…

JavaMVC之JSON

JSON 8.1、什么是JSON&#xff1f; JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式&#xff0c;目前使用特别广泛。采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。易于人阅读和编写…

龙芯团队完成CoreCLR MIPS64移植,在github开源

国产龙芯的软件生态之中.NET不会缺席&#xff0c;毕竟 C# 与 .NetCore/Mono 也是全球几大主流的编程语言和运行平台之一&#xff0c;最近一段时间听到太多的鼓吹政务领域不支持.NET&#xff0c; 大家都明白这是某些人为了自己的利益打压使用.NET技术的公司&#xff0c;我今天写…

来谈一谈专注力的真相

这是头哥侃码的第205篇原创在日常生活&#xff08;或工作&#xff09;中&#xff0c;你有没有遇上过这样的情况&#xff1f;比如你正在跟小伙伴讨论一个技术方案&#xff0c;聊着聊着&#xff0c;突然小A脑袋一抽筋&#xff0c;问你&#xff1a;“老大&#xff0c;咱们这周五晚…

Java访问控制修饰符

访问控制修饰符 Java中&#xff0c;可以使用访问控制符来保护对类、变量、方法和构造方法的访问。 Java 支持 4 种不同的访问权限。 default (即默认&#xff0c;什么也不写&#xff09;: 在同一包内可见&#xff0c;不使用任何修饰符。 使用对象&#xff1a;类、接口、变量…

数据结构与算法专题——第三题 最长公共子序列

一&#xff1a;作用最长公共子序列的问题常用于解决字符串的相似度&#xff0c;是一个非常实用的算法&#xff0c;作为码农&#xff0c;此算法是我们的必备基本功。二&#xff1a;概念举个例子&#xff0c;cnblogs这个字符串中子序列有多少个呢&#xff1f;很显然有27个&#x…

Java实现两个递增有序链表合并成一个递增有序链表和两个非递减有序链表合成一个非递增有序链表

代码如下: package sjjgniub;import java.util.LinkedList; import java.util.Scanner;SuppressWarnings("all") public class LinkList {private class Node{int data;Node next;public Node(){}public Node(int data){this.data data;next null;}}Node head nu…

职场PUA到底有多可怕?

阅读本文大概需要 5.2分钟。“小张&#xff0c;好好干啊&#xff0c;明年一定给你加薪&#xff01;” 。从小张入职这家公司起&#xff0c;这是老板对小张第三次这么说了。小张每天干到晚上12点&#xff0c;任劳任怨&#xff0c;虽然一直没涨过工资&#xff0c;但是老板的不断认…

数据结构与算法专题——第二题 优先队列

前段时间玩小爬虫的时候&#xff0c;我把url都是放在内存队列里面&#xff0c;有时我们在抓取url的时候&#xff0c;通过LCS之类的相似度比较&#xff0c;发现某些url是很重要的&#xff0c;需要后端解析服务器优先处理&#xff0c;针对这种优先级比较大的url&#xff0c;普通的…

数据结构与算法专题——第一题 Bitmap算法

在所有具有性能优化的数据结构中&#xff0c;我想大家使用最多的就是hash表&#xff0c;是的&#xff0c;在定位查找场景上具有O(1)的常量时间&#xff0c;多么的简洁优美&#xff0c;但是在特定的场合下&#xff1a;①&#xff1a;对10亿个不重复的整数进行排序。②&#xff1…

Telegraf和Grafana监控多平台上的SQL Server

问题SQL Server在很多企业中部署在多个平台上(Windows,Linux和Container)&#xff0c;需要一种能支持多平台的解决方案用于收集和展示相关的监控指标。我选择企业中比较流行的监控展示工具Grafana和监控指标收集工具Telegraf进行实现。这也是为了方便与企业中已经在存在监控平台…

酸吗?28岁程序员财务自由宣布退休!

财务自由是我们这代人的共同追求&#xff0c;有程序员28岁就做到了。近期的一条新闻就直接刷屏了&#xff0c;28岁今日头条程序员手握上亿期权宣布退休&#xff0c;引发一片羡慕嫉妒恨。履历如下&#xff1a;2008-2012就读于暨南大学政治与行政管理专业&#xff0c;自学编程。2…