浅谈.Net异步编程的前世今生----APM篇

前言

e8c3cca60ab7a632234d0d258090e635.png

在.Net程序开发过程中,我们经常会遇到如下场景:

编写WinForm程序客户端,需要查询数据库获取数据,于是我们根据需求写好了代码后,点击查询,发现界面卡死,无法响应。经过调试,发现查询数据库这一步执行了很久,在此过程中,UI被阻塞,无法响应任何操作。

如何解决此问题?我们需要分析问题成因:在WinForm窗体运行时,只有一个主线程,即为UI线程,UI线程在此过程中既负责渲染界面,又负责查询数据,因此在大量耗时的操作中,UI线程无法及时响应导致出现问题。此时我们需要将耗时操作放入异步操作,使主线程继续响应用户的操作,这样可以大大提升用户体验。

直接编写异步编程也许不是一件轻松的事,和同步编程不同的是,异步代码并不是始终按照写好的步骤执行,且如何在异步执行完通知前序步骤也是其中一个问题,因此会带来一系列的考验。

幸运的是,在.Net Framework中,提供了多种异步编程模型以及相关的API,这些模型的存在使得编写异步程序变得容易上手。随着Framework的不断升级,相应的模型也在不断改进,下面我们一起来回顾一下.Net异步编程的前世今生。

第一个异步编程模型:APM

概述

APM,全称Asynchronous Programing Model,顾名思义,它即为异步编程模型,最早出现于.Net Framework 1.x中。

它使用IAsyncResult设计模式的异步操作,一般由BeginOperationNameEndOperationName两个方法实现,这两个方法分别用于开始和结束异步操作,例如FileStream类中提供了BeginRead和EndRead来对文件进行异步字节读取操作。

c18f9113fd6d10b664ed116e3cd07848.png

使用

11051c8116b7ac62215c5ed9c2c1902b.png

在程序运行过程中,直接调用BeginOperationName后,会将所包含的方法放入异步操作,并返回一个IAsyncResult结果,同时异步操作在另外一个线程中执行。

每次在调用BeginOperationName方法后,还应调用EndOperationName方法,来获取异步执行的结果,下面我们一起来看一个示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace APMTest
{class Program{public delegate void ConsoleDelegate();static void Main(string[] args){ConsoleDelegate consoleDelegate = new ConsoleDelegate(ConsoleToUI);Thread.CurrentThread.Name = "主线程Thread";IAsyncResult ar = consoleDelegate.BeginInvoke(null, null);consoleDelegate.EndInvoke(ar);Console.WriteLine("我是同步输出,我的名字是:" + Thread.CurrentThread.Name);Console.Read();}public static void ConsoleToUI(){if (Thread.CurrentThread.IsThreadPoolThread){Thread.CurrentThread.Name = "线程池Thread";}else{Thread.CurrentThread.Name = "普通Thread";}Thread.Sleep(3000); //模拟耗时操作Console.WriteLine("我是异步输出,我的名字是:" + Thread.CurrentThread.Name);}}
}

在这段示例中,我们定义了一个委托来使用其BeginInvoke/EndInvoke方法用于我们自定义方法的异步执行,同时将线程名称打印出来,用于区分主线程与异步线程。

如代码中所示,在调用BeginInvoke之后,立即调用了EndInvoke获取结果,那么会发生什么呢?

如下图所示:

a38a17b36a32a73365840e03258cf825.png

看到这里大家也许会比较诧异:为什么同步操作会在异步操作之后输出呢?这样不是和同步就一样了吗?

原因是这样的:EndInvoke方法会阻塞调用线程,直到异步调用结束,由于我们在异步操作中模拟了3s耗时操作,所以它会一直等待到3s结束后输出异步信息,此时才完成了异步操作,进而进行下一步的同步操作。

同时在BeginInvoke返回的IAynscResult中,包含如下属性:

6586039434afa79f16a9cd5ef0c3b821.png

通过轮询IsCompleted属性或使用AsyncWaitHandle属性,均可以获取异步操作是否完成,从而进行下一步操作,相关代码如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace APMTest
{class Program{public delegate void ConsoleDelegate();static void Main(string[] args){ConsoleDelegate consoleDelegate = new ConsoleDelegate(ConsoleToUI);Thread.CurrentThread.Name = "主线程Thread";IAsyncResult ar = consoleDelegate.BeginInvoke(null, null);//此处改为了轮询IsCompleted属性,AsyncWaitHandle属性同理while (!ar.IsCompleted){Console.WriteLine("等待执行...");}consoleDelegate.EndInvoke(ar);Console.WriteLine("我是同步输出,我的名字是:" + Thread.CurrentThread.Name);Console.Read();}public static void ConsoleToUI(){if (Thread.CurrentThread.IsThreadPoolThread){Thread.CurrentThread.Name = "线程池Thread";}else{Thread.CurrentThread.Name = "普通Thread";}Thread.Sleep(3000); //模拟耗时操作Console.WriteLine("我是异步输出,我的名字是:" + Thread.CurrentThread.Name);}}
}

运行后结果如下:

28cab70918f0203c966e6ffd891def28.png

可以发现,在轮询属性时,程序仍然会等待异步操作完成,进而进行下一步的同步输出,无法达到我们需要的效果,那么究竟有没有办法解决呢?

此时我们需要引入一个新方法:使用回调。

在之前的操作中,使用BeginInvoke方法,两个参数总是传入的为null。若要使用回调机制,则需传入一个类型为AsyncCallback的回调函数,并在最后一个参数中,传入需要使用的参数,如以下代码所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace APMTest
{class Program{public delegate void ConsoleDelegate();static void Main(string[] args){ConsoleDelegate consoleDelegate = new ConsoleDelegate(ConsoleToUI);Thread.CurrentThread.Name = "主线程Thread";//此处传入AsyncCallback类型的回调函数,并传入需要使用的参数consoleDelegate.BeginInvoke(CallBack, consoleDelegate);//IAsyncResult ar = consoleDelegate.BeginInvoke(null, null);此处改为了轮询IsCompleted属性,AsyncWaitHandle属性同理//while (!ar.IsCompleted)//{//    Console.WriteLine("等待执行...");//}//consoleDelegate.EndInvoke(ar);Console.WriteLine("我是同步输出,我的名字是:" + Thread.CurrentThread.Name);Console.Read();}public static void ConsoleToUI(){if (Thread.CurrentThread.IsThreadPoolThread){Thread.CurrentThread.Name = "线程池Thread";}else{Thread.CurrentThread.Name = "普通Thread";}Thread.Sleep(3000); //模拟耗时操作Console.WriteLine("我是异步输出,我的名字是:" + Thread.CurrentThread.Name);}public static void CallBack(IAsyncResult ar){//使用IAsyncResult的AsyncState获取BeginInvoke中的参数,并用于执行EndInvokeConsoleDelegate callBackDelegate = ar.AsyncState as ConsoleDelegate;callBackDelegate.EndInvoke(ar);}}
}

运行后结果如下:

ff8657308c37f3d4ae2f0e10a5806511.png

此时可以看出,使用回调的方式已经实现了我们需要的效果。在同步执行时,将耗时操作放入异步操作,从而不影响同步操作的继续执行,在异步操作完成后,回调返回相应的结果。

小结

1d5c8aba65b6b590978402fb4cbb5410.png

APM模型的引入,使得编写异步程序变的如此简单,只需定义委托,将要执行的方法包含其中,并调用Begin/End方法对,即可实现异步编程。在一些基础类库中,也已经提供了异步操作的方法,直接调用即可。

同时我们可以看到,BeginInvoke方法,实际上是调用了线程池中的线程进行操作,因此APM模型也应属于多线程程序,同时包含主线程与线程池线程。

d8a1f14216a2b618bcdfd0d509d88534.png

但是APM模型也存在一些缺点:

c702b49a13da6638c57c4d52f93d3edf.png

  • 若不使用回调机制,则需等待异步操作完成后才能继续执行,此时未达到异步操作的效果。

  • 在异步操作的过程中,无法取消,也无法得知操作进度。

  • 若编写GUI程序,异步操作内容与主线程未在同一线程,操作控件时会引起线程安全问题。

为了解决这些缺陷,微软推出了其他的异步模式,预知后事如何,且听下回分解。

93100f63ae88ee640c7085b2d2888c9b.png

您的点赞和在看是我创作的最大动力,感谢支持

dd299c2b2882be369cd419a4507acfc4.png

95e2452b5b98852336bc5fdd1489d508.png

6bdc25294f0f6e3e376ee9a0ab72df6c.png

公众号:wacky的碎碎念

知乎:wacky

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

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

相关文章

浅谈.Net异步编程的前世今生----TPL篇

前言我们在此前已经介绍了APM模型和EAP模型,以及它们的优缺点。在EAP模型中,可以实时得知异步操作的进度,以及支持取消操作。但是组合多个异步操作仍需大量工作,编写大量代码方可完成。因此,在.Net Framework 4.0中&am…

Google:推荐几款好用的Chrome浏览器插件

1、Clear Cache 0.3.3.2 一键清空浏览器缓存数据。 https://chrome.google.com/webstore/detail/clear-cache/cppjkneekbjaeellbfkmgnhonkkjfpdn 2、Pig Toolbox 1.0.6.4 双击关闭页签,鼠标手势,手势动作轮,摇臂,超级拖拽&#xf…

豆瓣8.7!BBC这部成人社会禁片,曝光了行业内不能说的秘密

全世界只有3.14 % 的人关注了爆炸吧知识不知道生活中的你是否也会这样?平时即便不买东西,没事也会打开淘宝看看。但凡遇上双11、618各种促销节,总觉得不买好像就亏了,每每忍不住手痒,交了一堆智商税之后又开始后悔。如…

Lync-用户-电话号码-更新

1. 更新-用户-手机号 2. 服务器-更新-地址簿 3. 客户端-更改-注册表-<只操作一次!> 在命令提示符中输入如下命令&#xff1a; Reg Add HKLM\Software\Policies\Microsoft\Communicator /v GalDownloadInitialDelay /t REG_DWORD /d 0 /f 4. 客户端-删除-用户信息 退出-Ly…

豆瓣评分9分+,6部经典趣味数学纪录片!

全世界只有3.14 % 的人关注了爆炸吧知识数学是研究数量、结构、变化以及空间模型等概念的一门学科。透过抽象化和逻辑推理的使用&#xff0c;由计数、计算、量度和对物体形状及运动的观察中产生。数学家们拓展这些概念&#xff0c;为了公式化新的猜想以及从合适选定的公理及定义…

C# Hook原理及EasyHook简易教程

前言在说C# Hook之前&#xff0c;我们先来说说什么是Hook技术。相信大家都接触过外挂&#xff0c;不管是修改游戏客户端的也好&#xff0c;盗取密码的也罢&#xff0c;它们都是如何实现的呢&#xff1f;实际上&#xff0c;Windows平台是基于事件驱动机制的&#xff0c;整个系统…

squid 服务器的应用

实验名称&#xff1a;squid 服务器的应用 实验目标&#xff1a; 任务一&#xff1a;实现正向代理 任务二&#xff1a;实现透明代理 任务三&#xff1a;实现反向代理 提示1、在启动squid服务程序之前需要先确认Linux主机具有完整的域名&#xff0c;如果没有可以在hosts文件中进行…

有的人走着走着就散了!

1 有的人走着走着就走了上坡路▼2 没有感情的甩绳机器▼3 大男孩们陪小朋友踢球到底谁玩的比较开心▼4 给我妈妈演示一下我在肚子里的时候是怎么踹你的▼5 狗子&#xff1a;谢谢您嘞&#xff0c;我这是耳朵不是抹布&#xff01;▼6 从没想到会在这种情况下和你相遇▼7 所…

官宣 .NET 6 RC (Release Candidate) 2

我们很高兴发布 .NET 6 RC(Release Candidate) 2。它是生产环境中支持的两个“go live”候选版本中的第二个。在过去的几个月里&#xff0c;团队一直专注于质量的改进。这个版本中有很多的新特性&#xff0c;但在接近尾声时我们才会把他们完全整合在一起。该团队目前正在验证端…

SQL Server索引进阶第十篇:索引的内部结构

索引设计是数据库设计中比较重要的一个环节&#xff0c;对数据库的性能其中至关重要的作用&#xff0c;但是索引的设计却又不是那么容易的事情&#xff0c;性能也不是那么轻易就获取到的&#xff0c;很多的技术人员因为不恰当的创建索引&#xff0c;最后使得其效果适得其反&…

JDK安装及java环境配置_JDK安装及Java环境变量配置

2.点击Accept License Agreement&#xff0c;下载适合自己电脑版本的JDK.由于我的电脑是windows10 64位专业版。点击红色下载按钮。保存位置自己决定&#xff0c;只要自己安装时能找到就行。3.找到安装文件&#xff0c;双击。4.下一步&#xff0c;这里会让你选择安装目录。注意…

33张你没看过的酷炫化学动图, 秒懂化学反应原理!

化学的神奇魅力可是不是随便说说的&#xff0c;神奇起来让人叹为观止。下面就让腾远君带领大家看看传说中的37张神图&#xff0c;了解化学之美吧。1 . 硫氰酸汞分解&#xff08;“法老之蛇”&#xff09;原理&#xff1a;硫氰酸汞受热分解&#xff0c;部分产物燃烧。2Hg(SCN)2→…

从编译器层面理解C#中的闭包的这个坑!

前言在公众号上看到一篇文章《正确使用和理解C#中的闭包》&#xff0c;里面提到了闭包的一个坑&#xff1a;当捕获的外部变量为for循环的迭代变量时&#xff0c;C#认为变量i是定义在循环体外的。所以&#xff0c;当添加委托集合的for循环执行完时&#xff0c;i的值已经变为3了&…

C# 使用 HelpProvider 控件调用帮助文件

HelpProvider控件可以将帮助文件(.htm文件或.chm文件)与 Windows 应用程序相关联&#xff0c;为特定对话框或对话框中的特定控件提供区分上下文的帮助&#xff0c;打开帮助文件到特定部分。如目录、索引或搜索功能的主页。如图1 所示为 HelpProvider 控件。图1 HelpProvider…

哈哈哈,弟弟被卡桶里了......

1 哈哈哈哈哈弟弟被卡桶里面了&#xff08;via.小妮&#xff09;&#xff08;注意安全&#xff0c;请勿模仿&#xff01;&#xff09;▼2 弟弟是个狠人▼3 Self-potato: 一种不需要沙发也能无意义地待着的生活方式&#xff08;via.字幕少女&#xff09;▼4 防晒的正确打开方…

LVM基本应用 扩展及缩减实现

LVM: Logical Volume Manage首先&#xff1b;pv管理工具&#xff1a; pvs&#xff1a;简要pv信息显示 pvdisplay&#xff1a;显示pv的详细信息pvcreate /dev/DEVICE: 创建pvvg管理工具&#xff1a; vgs vgdisplayvgcreate [-s #[kKmMgGtTpPeE]] VolumeGroupName Physical…

java 线程 插件_我的第一个Chrome插件:天气预报应用

1.Chrome插件开发基础开发Chrome插件很简单&#xff0c;只要会基本的前台技术HTML、CSS、JS就可以开发了。Chrome插件一般包括两个HTML页面background和popup。background页面只在启动浏览器加载插件时载入一次&#xff0c;它不直接显示出来而是在后台运行。它包含了插件的主要…

保送北大,连发三篇Science,这位80后川妹子近日再发重磅级研究成果!

全世界只有3.14 % 的人关注了爆炸吧知识本文转自募格学术2020年9月21日&#xff0c;启函生物杨璐菡博士等在 Nature 子刊 Nature Biomedical Engineering杂志上发表了题为&#xff1a;Extensive germline genome engineering in pigs 的研究论文。杨璐菡杨璐菡带领的研究团队成…

Linq 下的 Take() 方法内部机制是怎样的?

咨询区 Rahul Kishore&#xff1a;我的web需要访问数据库&#xff0c;但是表比较大&#xff0c;我仅仅想要获取该表中 N 条数据&#xff0c;我查阅了 MSDN 文档&#xff0c;看到了一个 Take() 方法&#xff0c;我现在很疑惑它的运行机制是下面哪一种&#xff1f;先从数据库中获…

如何直接soap字符串,访问webservice

2019独角兽企业重金招聘Python工程师标准>>> 1.Webservice.GetVcardByUserNo(String userId&#xff0c;String userNo);这个是封装了的webservice接口。 2.在程序中连续两次调用该接口时&#xff0c;ksoap2在解析第二次调用返回的结果时抛异常。 异常信息如下&…