C#并发编程之异步编程(三)

写在前面

本篇是异步编程系列的第三篇,本来计划第三篇的内容是介绍异步编程中常用的几个方法,但是前两篇写出来后,身边的朋友总是会有其他问题,所以决定再续写一篇,作为异步编程(一)异步编程(二)的补充。

本篇内容主要讨论,在我们的异步代码里,运行的到底是哪个线程,在执行长时间运行操作时线程发生了什么。

Await之前

在一个被async修饰了的异步方法里,如果没有遇到await,你的代码将一直在调用线程上。在UI应用程序里,比如ASP.NET或者WinForm程序里,你的代码会在ASP.NET工作线程或WinForm工作线程上运行。

我们来看一下以下范例

   2:  {
   3:      Console.WriteLine();
   4:   
   5:      User user = this.GetUserAsync();
   6:   
   7:      //call other code
   8:   
   9:      return Task.CompletedTask;
  10:  }

以上范例里,我们在一个异步方法里调用了另一个异步方法,但是我们并没有使用await,这段代码依然在原始调用线程上执行,此时这个方法只是扮演了一个传播异步的作用。

当我们在UI线程上如此编程的时候,代码在UI线程是执行,在没有执行结束之前,页面是没有响应的。所以如果页面长时间没有响应,未必是异步导致的,可能会有其他原因,需要综合考虑,可以借助性能分析器来查看影响系统的原因在哪里。

Await中

代码到达await后,到底是哪一个线程在执行异步操作呢。

我们以ASP.NET为例,对于网络请求之类的操作,此时没有线程在执行异步操作,他们都被阻塞了,正在等待操作完成。但是如果使用了Task.Run,那么执行该任务时就要用到线程池里的线程了。

那么问题来了,我们在编写异步方法的时候,确确实实可以看到这个方法被执行了,肯定有线程执行才行啊。

对的,确实需要线程来执行,这个线程我们把它称之为是IO完成端口线程。此线程等待网络请求完成,同时它在所有网络请求之间共享。当网络请求完成时,操作系统中的中断处理程序会以Job方式添加到IO完成端口的队列中。在请求发起后,响应返回前,它们需要依次由单个IO完成端口处理。

实际上,一般情况下只有少量IO完成端口线程,以充分利用多个CPU核心。需要注意的是,无论当前有多少个请求,我们的线程数量都是固定的。 

参考以下运行图

640?wx_fmt=png

SynchronizationContext

我在异步编程(一)这边文章里,有讲到SynchronizationContext这个类,它是.NET框架提供的类,可以在特定类型的线程中运行代码。

.NET使用各种SynchronizationContext,常见的有ASP.NET、WinForms和WPF使用的UI线程上下文。SynchronizationContext的实例本身并没有特殊的地方,其实例指向的是其子类,具有静态成员,可以用于读取和控制当前的SynchronizationContext。

当前SynchronizationContext是当前线程的属性。在一个特定线程所运行到的任意的地方,都能够获取当前的SynchronizationContext并存储它,并且可以使用SynchronizationContext,在所启动的这个特定线程上运行代码。综上所述,我们并不需要知道代码在哪个线程上启动,只需要使用到SynchronizationContext,我们就可以返回到启动线程。

SynchronizationContext的重要方法是POST,它可以使委托在正确的上下文中运行。

某些SynchronizationContext封装单个线程,如UI线程。有些线程封装了特定类型的线程,例如线程池,但可以选择将委托发送到其中的任何一个线程。有些不会更改代码运行在哪个线程上,而只用于监视,如ASP.NET SynchronizationContext。

到这个地方,我们就需要了解一个问题了。在await之前,我们的代码是在调用线程上运行,那么await之后,恢复方法时到了哪个线程上了?

实际上,大多数情况下,await后的代码也由调用线程运行,尽管调用线程可能在等待期间做了其他事情。C#使用SynchronizationContext来完成此操作。当等待任务完成时,当前的同步上下文被存储为暂停方法的一部分。然后,当方法恢复时,await关键字的基础结构使用POST在捕获的同步上下文上恢复该方法。

既然有大多数情况,那么肯定也有小众情况吧,以下情况可以在不同的线程上运行

  • SynchronizationContext具有多个线程,如线程池

  • SynchronizationContext不是真正切换线程的上下文

  • 到达等待时,没有当前的同步上下文,例如在控制台应用程序中。

  • 将任务配置为不使用同步上下文来恢复

注意:

对于UI应用程序来说,在同一线程上恢复是最重要的,我们等待之后安全的操作UI。

解析异步操作

以WinForm为例,我们设计一个按钮,用于下载我们喜欢的小图标。用户点击按钮之后,UI线程启动,并会执行响应的操作,以下图片展示了一个异步操作的流程,以及期间UI线程与IO线程是如何切换的

640?wx_fmt=png

1、用户单击该按钮,事件处理程序GetButton_OnClick开始排队等待运行。

2、用户界面线程执行GetButton_OnClick的前半部分,包括对GetFaviconAsync的调用。

3、UI线程继续进入GetFaviconAsync并执行其前半部分,包括对DownloadDataTaskAsync的调用。

4、UI线程继续进入DownloadDataTaskAsync,它启动下载并返回任务。

5、UI线程离开DownloadDataTaskAsync,并返回GgetFaviconAsync处的await。

6、当前的UI线程捕获到了SynchronizationContext。

7、GetFaviconAsyncy因为有await的标识,会等待,当DownloadDataTaskAsync完成后GetFaviconAsyncy便会使用捕获到的SynchronizationContext恢复。

8、用户线程离开GetFaviconAsync,并返回一个任务,并运行到GetButton_OnClick中的await。

9、类似地,GetButton_OnClick被等待暂停。

10、用户线程离开GetButton_OnClick,可能会用于处理其他操作。【此时,我们正在等待图标下载。可能需要几秒钟。注意,UI线程可以自由处理其他用户操作,而IO完成端口线程尚未涉及到。操作期间阻塞的线程总数为零。】

11、下载完成,因此IO完成端口在DownloadDataTaskAsync中对逻辑进行排队处理。

12、IO完成端口线程将把DownloadDataTaskAsync返回的任务设置为完成。

13、IO完成端口线程在任务内部运行代码并处理完成,并会调用捕获到的同步上下文(UI线程)上的POST以继续运行接下来的代码。

14、IO完成端口线程被释放并可能在其他IO上工作。

15、用户界面线程找到POST指令,并继续执行GetFaviconAsync的后半部分,直到结束。

16、当UI线程离开GetFaviconAsync时,它会将GetFaviconAsync返回的任务设置为完成。

17、在这个运行点里,当前的同步上下文与捕获的上下文相同,因而无需用到POST,UI线程也会继续同步进行。【此逻辑在WPF中是无效的,因为WPF经常创建新的SynchronizationContext对象。尽管它们是等效的,这使得TPL认为它需要重新POST。】

18、用户线程继续运行GetButton_OnClick的后半部分,直到结束。 

总结

同步上下文的每个实现都是以不同的方式执行POST的,这是非常消耗性能的事情。为了避免这种开销,.NET内部也是有自己的优化机制的,它会在捕获的SynchronizationContext与任务完成时的当前上下文相同时,不使用POST。很有意思的是,如果你使用调试器查看这种情况,会发现调用堆栈是颠倒的。

但是,当同步上下文不同时,这就需要用到系统开销了。在性能关键的代码中或者某个代码库中,如果我们并不不关心使用到了哪个线程,这个时候我们也可以通过自己的手动操作来避开这种开销。

在等待任务之前调用ConfigureaWait来完成。这样就不会恢复到原始同步上下文。

   1:  byte[] bytes = await httpClient.PostAsJsonAsync(url,data).ConfigureAwait(false).ReadAsStreamAsync();

不过,ConfigureAwait并不是严格的指令,它是.NET设计的一个标识,用来告诉运行时我们不介意方法在哪个线程上运行。如果该线程不重要(线程池线程),它将会继续执行代码。如果是很重要的线程,.NET会通过自身机制将线程释放,让它来做其他事情,而方法也将在线程池中恢复。.NET使用线程的当前的SynchronizationContext来判断它是否重要。

前文有说过,本文再提一次,在同步代码中运行异步代码,可能有隐藏的问题。Task有一个Result属性,该属性阻止等待任务完成。如以下代码:

   1:  var result = GetUserAsync().Result;

但是如果在只有一个线程(如UI线程)的SynchronizationContext使用就会发生死锁现象。解决问题的方法就是,我们可以使用线程池线程来解决这个问题。如以下代码:

   1:  var result = Task.Run(() =>GetUserAsync()).Result;

640?wx_fmt=jpeg


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

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

相关文章

UVA - 10253 Series-Parallel Networks(递推式、记忆化搜索写法)

题目:UVA-10253 题目翻译(来自蓝书): 串并联网络有两个端点,一个叫源,一个叫汇,递归定义如下: (1) 一条单独的边是串并联网络。 (2)…

C#规范整理·泛型委托事件

基于泛型,我们得以将类型参数化,以便更大范围地进行代码复用。同时,它减少了泛型类及泛型方法中的转型,确保了类型安全。委托本身是一种引用类型,它保存的也是托管堆中对象的引用,只不过这个引用比较特殊&a…

Abp v0.18.0 新版本: MVC Module 启动模板

0.18.0将于近期发布, 本文内容针对于0.18.0版本.MVC模块启动模板可用此模板开发基于模块开发最佳实践和约定的可复用 应用程序模块 . 它同样适用于开发微服务.如何开始?你可以使用ABP CLI创建基于此启动模板的新项目,或者你也可以在入门页面创建并下载项目. 在这里我们使用CLI…

Consul-template+nginx实现自动负载均衡

前言consul-template 是 Consul 的一个守护程序,使用 consul-template 可以方便快速的实现对 Consul Key/Value 存储系统的访问,可以从 KV 系统中读取数据、监视变动、同步本地文件;还可以在执行模板更新的同时,执行本地系统命令&…

Codeforces Round #613 (Div. 2) E. Delete a Segment 离散化

传送门 文章目录题意:思路:题意: 思路: 我们考虑将区间内的位置都111,之后求区间段数就可以转换成求,连续不为000的区间段数,由于范围有[−1e9,1e9][-1e9,1e9][−1e9,1e9]的级别,所…

是时候挥别 SQL Server 2008 了

微软 宣布将于 2019 年 7 月终止对 SQL Server 2008 和 2008 R2 的支持,使用 SQL Server 2008 的公司应该认真开始考虑下一步了。本文引用了一家数字化转型咨询公司高级顾问对此的分析,并提供了一些解决方案和注意事项。如果你还在使用 微软 2008 版的 S…

领域驱动设计,让程序员心中有码(八)

领域驱动是十五年前,由Eric Evans提出的解决软件工程复杂性问题的方法,作者从自己多年软件开发的角度出发,通过引入领域驱动设计的概念以及一系列战略设计模式和战术方法,为混沌的软件开发领域带来了一缕阳光。在过去的许多年&…

H - Great Cells Gym - 101194H(数学推导/思维)

VJ地址 ps:在训练时写这道题 差点被送走了 我们可以发现给出的公式可以转化这样 这个就不说了,就是k^(nm)所以排列的可能; 然后我们要重点观察这个公式,可以发现g * Ag(方案数 * good点的数量) 可以意味这组成一个…

基于Dapper的开源Lambda扩展,且支持分库分表自动生成实体之基础

LnskyDB是基于Dapper的Lambda扩展,支持按时间分库分表,也可以自定义分库分表方法.而且可以T4生成实体类免去手写实体类的烦恼.文档地址: https://liningit.github.io/LnskyDB/开源地址: https://github.com/liningit/LnskyDBnuget地址: https://www.nuget.org/packages/LnskyDB…

技术达人“创造营”官宣:Microsoft Learn 学习平台C位出道

真的大神敢于直面随时更新的IT技术你是要做一个平平无奇的“合格员工”?还是要做一个博古通今的“江湖百晓生”?转型变化,是时代的氛围面对日新月异的技术更新一不留神,就会沦落为“技术支持滞后人员”在微软,有这样一…

P2839 [国家集训队]middle 二分 + 主席树 在值域上建区间

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 我们先解决怎么判断中位数的问题&#xff0c;我们可以二分一个midmidmid&#xff0c;将<mid<mid<mid的值都变成−1-1−1&#xff0c;其他的数都变成111&#xff0c;那么当全部的和…

读再多书都没觉得自己变强?试试我这“5年陈”的方法

这里是Z哥的个人公众号每周五早8点 按时送达当然了&#xff0c;也会时不时加个餐&#xff5e;我的第「76」篇原创敬上4800字巨献奉上hi&#xff0c;大家好。三周过去了&#xff0c;上次教你的几个「速读」技巧练习的怎么样了&#xff1f;上周&#xff0c;已经有不少小伙们开始催…

Educational Codeforces Round 77 (Rated for Div. 2) C. Infinite Fence 数论

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 碰到这样的题肯定是先写几个找找规律了&#xff0c;随便写几个就可以发现是以lcm(a,b)lcm(a,b)lcm(a,b)为一个循环&#xff0c;所以我们只需要在一个周期lcm(a,b)lcm(a,b)lcm(a,b)中求最长…

C# 内存的理解 通俗说

一.概念堆栈是什么&#xff1f;在说堆栈之前&#xff0c;先说说内存是神马&#xff1f;内存&#xff1a;程序在运行的过程&#xff0c;电脑需要不断通过CPU进行计算&#xff0c;这个计算的过程会读取并产生运算的数据&#xff0c;这些数据需要一个存储容器存放。这个容器&#…

P3899 [湖南集训]谈笑风生 主席树解决二维数点

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 由于a,ba,ba,b都比ccc厉害&#xff0c;那么a,ba,ba,b一定是某个是某个的祖先。那么就分为两种情况了&#xff1a; (1)(1)(1) bbb在aaa上面&#xff0c;约定depth[1]1depth[1]1depth[1]1&am…

打造 .NET Core 链接转发服务

我最近使用 .NET Core 2.2 造了个名为"Link Forwarder" &#xff08;链接转发器&#xff09;的 URL 转发服务&#xff0c;并已开源。目前预览版已部署到我的子域"go.edi.wang"。本文将分享我如何构建这个项目&#xff0c;以及我学到的东西。为了帮助大家了…

P3293 [SCOI2016]美味 主席树 + 伪01trie

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 看到异或的话&#xff0c;很容易想到用01trie来贪心的搞&#xff0c;但是这个题涉及区间问题&#xff0c;直接搞的话需要将[l,r][l,r][l,r]的数都插入trie里面&#xff0c;这样的复杂度显然…

Abp 0.18.0 正式发布! -ABP CLI,新模板和其他功能

ABP CLI, v0.18版本新模板和其他功能ABP v0.18已发布, 包含解决的80个issue, 550次提交.网站更改abp.io网站完全更新以突出ABP框架的目标和重要功能.文档和博客网址也会更改&#xff1a;abp.io/documents移至docs.abp.io.abp.io/blog转移到blog.abp.io.ABP CLIABP CLI(命令行界…

短信验证码“最佳实践”

1、背景年初&#xff0c;从外地转移阵地到西安&#xff0c;转眼已两个多月。很久不写业务代码了&#xff0c;到了新公司&#xff0c;条件恶劣到前所未有&#xff0c;从需求&#xff0c;设计&#xff0c;架构&#xff0c;实现&#xff0c;实施&#xff0c;测试&#xff0c;bug修…

Insider Dev Tour 2019巡演中国站

Insider Dev Tour 2019全球巡演中国站落下帷幕&#xff0c;在线直播加六大城市会场&#xff0c;作为四十余位讲师的一份子&#xff0c;我在线分享了Microsoft Graph及Microsoft Teams开发平台的内容&#xff0c;很高兴与几千位开发者共同度过了一个特别的星期天&#xff0c;很不…