关于C#异步编程你应该了解的几点建议

前段时间写了一篇关于C#异步编程入门的文章,你可以点击《C#异步编程入门看这篇就够了》查看。这篇文章我们来讨论下关于C#异步编程几个不成文的建议,希望对你写出高性能的异步编程代码有所帮助。注:本文的很多内容都是学习《Effective C#》的总结。

作者:依乐祝

原文地址:https://www.cnblogs.com/yilezhu/p/12099219.html

尽量不要编写返回值类型为void的异步方法

在通常情况下,建议大家不要编写那种返回值类型为void的异步方法,因为这样做会破坏该方法的启动者与方法本身之间的约定,这套约定本来可以确保主调方能够捕获到异步方法所发生的异常。
正常的异步方法是通过它返回的Task对象来汇报异常的。如果执行过程中发生了异常,那么Task对象就进入了faulted(故障)状态。主调方在对异步方法所返回的Task对象做await操作时,该对象若已处在faulted状态,系统则会将执行异步方法的过程中所发生的异常抛出,反之,若Task尚未执行到抛出异常的那个地方,则主调方的执行进度会暂停在await语句这里,等系统稍后安排某个线程继续执行该语句下方的那些代码时,异常才会抛出。

总结一句话就是:void的异步方法发生异常时,开发者得不到任何通知,程序既不会触发普通的异常处理程序,也不会把这些异常记录下来。总之,这会让相关的线程默默的终止掉。

不要把同步方法与异步方法组合起来使用

async关键字来修饰的方法意味着该方法有可能会在执行完所有工作之前就把控制权返回给主调方,而且,它返回给主调方的是个代表工作进度的Task对象。主调方可以查询此对象的状态,以了解该工作是否已经完成、尚未完成还是在执行过程中发生了故障。此外,这种方法还在暗示主调方:本方法所执行的工作可能要花费很长时间,因此建议你先去做其他一些事情,稍后再来向我索要结果。
与此相反,如果把某个方法设计成同步方法,那么意味着当该方法执行完毕时,它的后置条件必定能够得到满足。无论这个方法要花多长时间去完成工作,它都会采用与主调方相同的资源来完成,主调方必须等这个方法彻底执行完毕才能向下执行。
这两种方法单独写起来都很清晰,但是如果把他们组合在一起就会让方法变得十分难用,而且有可能导致各种bug,如死锁。因此,这里提出两条重要的原则。第一,不要让同步方法必须等待异步方法执行完毕才能往下执行(尽量不用Wait()以及.result这些阻塞式的方法)。第二,不要让异步方法把虽然耗时很长、计算量很大但是完全可以由自己执行的工作转交给另一个异步任务去做。’
当然对于第二点,这并不是说计算量较大的任务绝对不能放在单独的线程中执行,而是说不应该把只用一个线程就能迅速做好的任务刻意的拆解成许多个较小的部分,并把他们分别放在多个新的线程上执行,而是应该把整个任务都交给某个线程来执行才对。

使用异步方法时应尽量避免线程分配

异步任务看上去好像很神奇,因为这种任务刻意转移到另一个地方去做,使得开启这项任务的异步方法可以在该任务完成之后,从早前暂停的地方继续往下推进。不过,要想发挥异步任务的功效,就必须保证把这项任务交出去确实能够少占用一些资源,而不是仅仅会在相似的资源之间进行上下文切换。
如:对于一个控制台程序,如果只是执行一项计算量较大且耗时较长的任务(或者说,运行时间较长的CPU密集型的任务),那么把该任务单独放在另一个线程中并没有多大好处。因为这样做只能让工作线程始终处于繁忙状态,而主线程则必须一直卡在那里等待工作线程把任务做完。在这种情况下,实际上是用两个线程来完成原本只需要一个线程就能做好的工作,造成了资源的浪费。

避免不必要的上下文切换

目前C#代码中使用async以及await实现的异步方法默认是把await之后的代码放在早前捕获的那个上下文中执行的,这是因为这样做比较稳妥,它最多只会引发几次无谓的上下文切换,而不会使程序出现重大的错误,与之相反,如果系统不把山下文切换回去,那么万一遇到的是只能在特定的上下文中才能执行的代码,那么程序就有可能崩溃。因此,无论有没有必要切换上下文,系统都会切换至早前捕获到的那个上下文,并把await之后的语句放在那个上下文执行。
如果不想让系统做出这样的安排,那么可以调用ConfigureAwait()方法。这表示接下来的那些代码无须放在早前捕获的上下文中执行。例如在很多程序集中,await语句之后的那些代码一般都与上下文无关,因此与,可以调用Task对象的ConfigureAwait()方法告诉系统,在执行完这项任务之后,不必专门把await下面的代码放在早前捕获的上下文中运行。如下所示:

public static async Task<XElement> ReadPacket(string url)
{var result=await DownloadAsync(url).ConfigureAwait(false);return XElement.Parse(result);
}

C#语言默认让程序把await下面的语句都放在早前捕获的上下文中执行,这样做虽然较为安全,但是会降低程序的效率。因此为了让用户能够更加顺畅的使用程序,我们应该调整代码的结构,把必须运行在特定上下文的代码剥离出来,并尽量考虑在await语句那里调用ConfigureAwait(false),使得程序可以把语句下面的代码放在默认上下文中运行,而不是切换回早前的上下文。

通过Task对象来进行异步开发

Task(任务)是一种抽象机制,可以用来表示某项工作,于是,就能够把该工作转交给其他资源去完成。Task类型以及与之相关的类与结构体提供了丰富的API,让开发者可以操控Task对象以及由该对象所表示的工作。此外,Task对象自身也具备一些方法与属性,可以用来操作本对象所表示的任务。这些Task对象可以合起来构成一项比较大的任务,他们之间既能够按照顺序执行,也能够平行的执行。
可以通过await语句来确保某些任务之间能够按照一定的顺序执行,也就是说,只有当该语句所要等待的那项工作完毕之后,语句下方的代码才能够执行。
总之,由于C#提供了一套丰富的API,因此可以写出相当优雅的算法来处理Task对象,并对这些对象所表示的任务进行安排。对任务的用法理解的越透彻,写出来的异步代码越清晰。
这里简单说明两个常用的API:

  1. WhenAll:会根据现有的一批任务创建出一项新的任务,只有当那批任务全部执行完毕时,这项新人物才能够完成。对Task.WhenAll所返回的新任务进行await操作会获得一份列表,早前的那些任务的执行结果就位于该列表中。

  2. WhenAny:为了尽早的获得某个结果,可能启动多项任务,使得他们分别从不同的途径去获取该结果。只要其中有一项任务完成,你的目标就达成了,针对这项需求,可以考虑使用Task.WhenAny方法,并把自己所创建的那批任务传进去。对WhenAny方法所返回的Task对象进行await操作可以获取到一项任务,它指的就是这批任务中最先执行完毕的那项任务。

考虑实现任务的取消协议

异步任务的编程模型(也叫基于任务的异步编程模型)提供了标准的API,用来取消任务或者广播任务的执行进度。虽然这些API是可选的,但如果某项任务确实能够汇报其进度,或者能够予以取消,那就可以考虑用合适的办法来实现这些API。

针对需要取消的任务,我们可以通过CanclelationTokenSource对象来进行取消操作。这种对象是一种起到中介作用的对象。该对象处在有可能发出取消请求的客户代码与支持取消功能的那项操作之间。

如果正在执行的任务发现客户端想要取消该操作,那么它就会通过ThrowIfCanclellationRequested()方法抛出TaskCanclledException异常,庸医表示整个工作流程没有能够完全得到执行。

此外,返回值类型为void类型的异步方法不应该支持取消功能。

缓存泛型异步方法的返回值

可能你在进行异步编程的时候对异步方法设置的返回类型都是Task或者Task<T>,然而有些时候把返回值类型设为Task可能会影响性能。如果某个循环或某段代码需要频繁的运行,那么系统就有可能分配很多个Task对象,从而占用相当多的资源。好在C#提供了一种新的类型,叫做ValueTask<T>对象,他用起来比普通的Task更为高效。该类型是值类型,因此创建这种类型的对象时,不需要再分配额外的空间。这个好处使得我们可以多创建一些这样的对象,而不用担心它会像Task对象那样占据过多的资源。如果你的异步方法可以根据早前缓存起来的结果直接返回相应的值,那么尤其应该考虑把返回值类型设置为ValueTask<T>

其次,ValueTask提供了一个能够接受Task参数的构造函数,这个构造函数会在其内部等候该Task的执行结果。

总结

今天分享的内容比较多,而且很多都比较难理解,不过确实是写出高性能异步方法所必须要掌握的技巧。由于时间较短,因此也没来得及通过代码进行讲述,所以需要有一定的基础才能看懂,不过还是希望对您有所帮助。

好看你就点点我

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

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

相关文章

数据库分区

一、分区原理分区并不是生成新的数据表&#xff0c;而是将表的数据均衡分摊到不同的硬盘&#xff0c;系统或是不同服务器存储介子中&#xff0c;实际上还是一张表。要实现这一功能&#xff0c;首先要了解数据库对水平分区表进行分区存储的原理。数据库分区和分表相似&#xff0…

如何在 C# 平台调用云开发?

▌关于作者苏震巍&#xff0c;云开发Linker计划成员&#xff0c;《微信开发深度解析》作者、Senparc.Weixin 微信 SDK 作者、微软最有价值专家&#xff08;MVP&#xff09;、盛派网络创始人兼首席架构师、微软 Ignite 技术大会讲师、从事软件及互联网研发已有26年&#xff0c;发…

如何打造组织级敏捷,你想知道的都在这里!

“敏捷是适应和响应变化的能力……敏捷组织将变化视为机遇&#xff0c;而不是威胁。” — Jim Highsmith注&#xff1a;Highsmith 在软件开发和 IT 行业有着超过 30 年的经验&#xff0c;曾是敏捷宣言的签署人之一&#xff0c;敏捷联盟的发起人和第一任理事&#xff0c;在很多行…

Azure DevOps Server CI - 自搭跨平台容器代理Agents

前言最近在地端(On-premises)幫團隊搭一套CI/CD流程&#xff0c;也順帶整理了一下從無到有的搭建過程&#xff0c;這次使用了docker技術來解決現有團隊使用CI/CD時讓現有CI/CD hosting環境過於複雜的問題。在開始之前&#xff0c;我先預備一下搭建的環境&#xff0c;如下:Windo…

.Net Core 认证组件源码解析

不知不觉.Net Core已经推出到3.1了,大多数以.Net为技术栈的公司也开始逐步的切换到了Core,从业也快3年多了,一直坚持着.不管环境怎么变,坚持自己的当初的选择,坚持信仰 .Net Core是个非常优秀的框架,如果各位是从WebForm开始,一步步走到今天,自然而然就会发现.微软慢慢的开始将…

在.NET Core下的机器学习--学习笔记

摘要.NET Core 在机器学习的应用场景&#xff0c;除了 ML .NET 还会介绍一个非常棒的開源技術 TensorFlow .NET &#xff0c; Keras .NET.讲师介绍本课内容人工智能介绍ML .NETICSharpCoreTensorFlow .NETKeras .NETSciSharp人工智能应用图像识别/物体识别自然语言/翻译搜索/知…

asp.net core 自定义基于 HttpContext 的 Serilog Enricher

asp.net core 自定义基于 HttpContext 的 Serilog EnricherIntro通过 HttpContext 我们可以拿到很多有用的信息&#xff0c;比如 Path/QueryString/RequestHeader 等请求信息, StatusCode/ResponseHeader 等响应信息&#xff0c;借助 HttpContext 我们可以在日志中记录很多有用…

我的 .NET Core 博客性能优化经验总结

点击上方蓝字关注“汪宇杰博客”导语去年8月&#xff0c;我用 .NET Core 重写了我的博客系统。经过一年多的优化&#xff0c;服务器响应速度从上线时候的 80ms 提高到了现在的 8ms&#xff0c;十倍提速。可惜由于部署在国外&#xff0c;自然不可抗力会导致中国用户晚上访问速度…

Redis 6 RC1发布,带来众多新特性

Redis 6 RC1 发布了&#xff0c;项目创建人 antirez 在博客中介绍&#xff0c;这是迄今最“企业”化的版本&#xff08;SSL 与 ACL 等特性与企业极相关&#xff09;&#xff0c;也是最大的版本&#xff0c;同时也是参与人数最多的版本。GA 版本预计在明年三月到五月之间发布。R…

Serverless那么火,2019年的采用如何?

传统的 IT 架构已经不适合当今快速发展的数字经济环境。技术顾问 Declan Morris 表示&#xff1a;“客户不仅期望零停机时间&#xff0c;而且要求应用程序无论在世界任何地方都具有一致的性能。”他还预测到&#xff0c;接下来是 Serverless 计算和函数即服务&#xff08;FaaS&…

【C】Natasha 插件编程

文章转载授权级别&#xff1a;CNatasha 是一个代替 Emit / Expression 的动态构建项目&#xff0c;旨在为开发者提供方便、快捷、高性能的动态构建服务&#xff0c;动态编程是生态的重要组成部分&#xff0c;希望开发者们能有足够的兴趣来了解、使用、建设它。1、插件生成使用 …

二叉树先序,中序,后序,层次遍历(数据结构)

先序遍历 先序遍历可以想象为&#xff0c;一个小人从一棵二叉树的根节点为起点&#xff0c;沿着二叉树的外沿&#xff0c;逆时针走一圈回到根节点&#xff0c;路上遇到的元素顺序&#xff0c;就是先序遍历的结果 先序遍历的结果为&#xff1a;A B D H I E J C F K G 中序遍…

.NET Core 在 K8S 上的开发实践--学习笔记

摘要本主题受众是架构师&#xff0c;开发人员&#xff0c;互联网企业 IT 运维人员。大纲&#xff1a;1、 K8S 对应用的要求&#xff1b;2、 .NET Core 上 K8S 的优势&#xff1b;3、 K8S 下的 .NET Core 配置&#xff1b;4、 .NET Core 上分布式组件概览。大纲kubernetes 对应用…

.NetCore 3.1 安装本地化中文智能提示

A、平时在群里&#xff0c;很多小伙伴都会问&#xff0c;.net core的智能提示和注释都是英文的&#xff0c;如果英语水平不是很高的&#xff0c;看着是挺麻烦&#xff0c;所以经常需要在身边有一个翻译软件&#xff0c;如果有汉化的中文智能提示就好了&#xff08;当然&#xf…

asp.net core 3.x Endpoint终结点路由1-基本介绍和使用

前言我是从.net 4.5直接跳到.net core 3.x的&#xff0c;感觉asp.net这套东西最初是从4.5中的owin形成的。目前官方文档重点是讲路由&#xff0c;没有特别说明与传统路由的区别&#xff0c;本篇主要介绍终结点路由的相关概念和如何使用&#xff0c;不会详细介绍路由&#xff0c…

共享后缀的链表

有一种存储英文单词的方法&#xff0c;是把单词的所有字母串在一个单链表上。为了节省一点空间&#xff0c;如果有两个单词有同样的后缀&#xff0c;就让它们共享这个后缀。下图给出了单词“loading”和“being”的存储形式。本题要求你找出两个链表的公共后缀。 函数接口定义&…

C#反射与特性(一):反射基础

1. 说明1.1 关于反射、特性在 《C# 7.0 本质论》中&#xff0c;关于这方面的知识在 《第十八章 反射、特性和动态编程》&#xff1b;在《C# 7.0 核心技术指南》中&#xff0c;这部分内容在《第19章 反射和元数据》。[图片来自 《C# 7.0 本质论》]在这里我们可以获得一些关联性很…

收藏!推荐12个超实用的Visual Studio插件

工欲善其事&#xff0c;必先利其器,整理的一些我必装的12款Visual Studio插件&#xff0c;希望你们能get到。效率工具前文传送门&#xff1a;推荐&#xff1a;程序员必装的10款谷歌插件程序员必备的8个学习工具99%的人不知道搜索引擎的6个技巧01 CodeMaidCodeMaid快速整理代码文…

搭建独立博客,这款评论插件不能错过

微信公众号因为申请的时间晚&#xff0c;一直到现在都无法开通评论功能&#xff0c;之前博客一直使用的多说作为评论系统&#xff0c;自从多说关闭后&#xff0c;好多年都处于无评论状态&#xff0c;最近发现 gitalk 还不错&#xff0c;所以在博客中进行了对 gitalk 的集成&…

最大堆和最小堆(数据结构)

堆和栈的区别&#xff1a; 一、空间分配区别&#xff1a; 栈&#xff08;操作系统&#xff09;&#xff1a;由操作系统自动分配释放&#xff0c;存放函数的参考值&#xff0c;局部变量的值等。其操作方式类似于数据结构中的栈堆&#xff08;操作系统&#xff09;&#xff1a;一…