浅谈.Net异步编程的前世今生----APM篇
前言
在.Net程序开发过程中,我们经常会遇到如下场景:
编写WinForm程序客户端,需要查询数据库获取数据,于是我们根据需求写好了代码后,点击查询,发现界面卡死,无法响应。经过调试,发现查询数据库这一步执行了很久,在此过程中,UI被阻塞,无法响应任何操作。
如何解决此问题?我们需要分析问题成因:在WinForm窗体运行时,只有一个主线程,即为UI线程,UI线程在此过程中既负责渲染界面,又负责查询数据,因此在大量耗时的操作中,UI线程无法及时响应导致出现问题。此时我们需要将耗时操作放入异步操作,使主线程继续响应用户的操作,这样可以大大提升用户体验。
直接编写异步编程也许不是一件轻松的事,和同步编程不同的是,异步代码并不是始终按照写好的步骤执行,且如何在异步执行完通知前序步骤也是其中一个问题,因此会带来一系列的考验。
幸运的是,在.Net Framework中,提供了多种异步编程模型以及相关的API,这些模型的存在使得编写异步程序变得容易上手。随着Framework的不断升级,相应的模型也在不断改进,下面我们一起来回顾一下.Net异步编程的前世今生。
第一个异步编程模型:APM
概述
APM,全称Asynchronous Programing Model,顾名思义,它即为异步编程模型,最早出现于.Net Framework 1.x中。
它使用IAsyncResult设计模式的异步操作,一般由BeginOperationName和EndOperationName两个方法实现,这两个方法分别用于开始和结束异步操作,例如FileStream类中提供了BeginRead和EndRead来对文件进行异步字节读取操作。
使用
在程序运行过程中,直接调用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(); //模拟耗时操作
Console.WriteLine("我是异步输出,我的名字是:" + Thread.CurrentThread.Name);
}
}
}
在这段示例中,我们定义了一个委托来使用其BeginInvoke/EndInvoke方法用于我们自定义方法的异步执行,同时将线程名称打印出来,用于区分主线程与异步线程。
如代码中所示,在调用BeginInvoke之后,立即调用了EndInvoke获取结果,那么会发生什么呢?
如下图所示:

看到这里大家也许会比较诧异:为什么同步操作会在异步操作之后输出呢?这样不是和同步就一样了吗?
原因是这样的:EndInvoke方法会阻塞调用线程,直到异步调用结束,由于我们在异步操作中模拟了3s耗时操作,所以它会一直等待到3s结束后输出异步信息,此时才完成了异步操作,进而进行下一步的同步操作。
同时在BeginInvoke返回的IAynscResult中,包含如下属性:

通过轮询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(); //模拟耗时操作
Console.WriteLine("我是异步输出,我的名字是:" + Thread.CurrentThread.Name);
}
}
}
运行后结果如下:

可以发现,在轮询属性时,程序仍然会等待异步操作完成,进而进行下一步的同步输出,无法达到我们需要的效果,那么究竟有没有办法解决呢?
此时我们需要引入一个新方法:使用回调。
在之前的操作中,使用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(); //模拟耗时操作
Console.WriteLine("我是异步输出,我的名字是:" + Thread.CurrentThread.Name);
} public static void CallBack(IAsyncResult ar)
{
//使用IAsyncResult的AsyncState获取BeginInvoke中的参数,并用于执行EndInvoke
ConsoleDelegate callBackDelegate = ar.AsyncState as ConsoleDelegate;
callBackDelegate.EndInvoke(ar);
}
}
}
运行后结果如下:

此时可以看出,使用回调的方式已经实现了我们需要的效果。在同步执行时,将耗时操作放入异步操作,从而不影响同步操作的继续执行,在异步操作完成后,回调返回相应的结果。
小结
APM模型的引入,使得编写异步程序变的如此简单,只需定义委托,将要执行的方法包含其中,并调用Begin/End方法对,即可实现异步编程。在一些基础类库中,也已经提供了异步操作的方法,直接调用即可。
同时我们可以看到,BeginInvoke方法,实际上是调用了线程池中的线程进行操作,因此APM模型也应属于多线程程序,同时包含主线程与线程池线程。
但是APM模型也存在一些缺点:
1、若不使用回调机制,则需等待异步操作完成后才能继续执行,此时未达到异步操作的效果。
2、在异步操作的过程中,无法取消,也无法得知操作进度。
3、若编写GUI程序,异步操作内容与主线程未在同一线程,操作控件时会引起线程安全问题。
为了解决这些缺陷,微软推出了其他的异步模式,预知后事如何,且听下回分解。
下集预告
浅谈.Net异步编程的前世今生----EAP篇
浅谈.Net异步编程的前世今生----APM篇的更多相关文章
- 浅谈.Net异步编程的前世今生----EAP篇
前言 在上一篇博文中,我们提到了APM模型实现异步编程的模式,通过使用APM模型,可以简化.Net中编写异步程序的方式,但APM模型本身依然存在一些缺点,如无法得知操作进度,不能取消异步操作等. 针对 ...
- 新手浅谈Task异步编程和Thread多线程编程
初学Task的时候上网搜索,看到很多文章的标题都是task取代thread等等相关,我也一直以为task和thread是一类,其实task是.net4.0提出的异步编程,在之前.net1.0有dele ...
- 浅谈JavaScript异步编程
单线程模式 我们知道JS的执行环境是单线程的,是因为JS语言最早是运行在浏览器端的语言,目的是为了实现页面上的动态交互.实现动态交互的核心就是DOM操作,因此决定了JS必须是单线程模式工作.我们来假设 ...
- [转帖]浅谈响应式编程(Reactive Programming)
浅谈响应式编程(Reactive Programming) https://www.jianshu.com/p/1765f658200a 例子写的非常好呢. 0.9312018.02.14 21:22 ...
- JavaScript 异步编程的前世今生(上)
前言 提到 JavaScript 异步编程,很多小伙伴都很迷茫,本人花费大约一周的业余时间来对 JS 异步做一个完整的总结,和各位同学共勉共进步! 目录 part1 基础部分 什么是异步 part2 ...
- 浅谈 js 字符串 trim 方法之正则篇
原文:浅谈 js 字符串 trim 方法之正则篇 关于 trim 其实没啥好说的,无非就是去除首位空格,对于现代浏览器来说只是简单的正则 /^\s+|\s+$/ 就可以搞定了.而且支持中文空格 等 ...
- 【温故知新】c#异步编程模型(APM)--使用委托进行异步编程
当我们用到C#类许多耗时的函数XXX时,总会存在同名的类似BeginXXX,EndXXX这样的函数. 例如Stream抽象类的Read函数就有 public abstract int Read(byt ...
- C#异步编程の-------异步编程模型(APM)
术语解释: APM 异步编程模型, Asynchronous Programming Model EAP 基于事件的异步编程模式, Event ...
- 浅谈Windows API编程
WinSDK是编程中的传统难点,个人写的WinAPI程序也不少了,其实之所以难就难在每个调用的API都包含着Windows这个操作系统的潜规则或者是windows内部的运行机制…… WinSDK是编程 ...
随机推荐
- 了解与建设有中国特色的Android M&N(Android6.0和7.0新特性分析)
http://geek.csdn.NET/news/detail/110434 Android N已经发布有段时间,甚至马上都要发布android 7.1,相信不少玩机爱好者已经刷入最新的Androi ...
- JAVA 语法2
1.算术运算符 运算符 运算规则 范例 结果 + 正号 +3 3 + 加 2+3 5 + 连接字符串 "中"+"国" "中国" - 负号 i ...
- Mysql分页查询性能分析
[PS:原文手打,转载说明出处,博客园] 前言 看过一堆的百度,最终还是自己做了一次实验,本文基于Mysql5.7.17版本,Mysql引擎为InnoDB,编码为utf8,排序规则为utf8_gene ...
- Interesting卡常数
C++ Interesting卡常数 作为一名OIer,在Noip中卡(kǎ 我就爱读kǎ)常数可以说是必备技巧.在此总结一下我所知卡常数的神奇手法: IO优化 fread 和 fwrite ,如果还 ...
- Coursera-AndrewNg(吴恩达)机器学习笔记——第二周
一.多变量线性回归问题(linear regression with multiple variables) 搭建环境OctaveWindows的安装包可由此链接获取:https://ftp.gnu. ...
- (二)SpringBoot基础篇- 静态资源的访问及Thymeleaf模板引擎的使用
一.描述 在应用系统开发的过程中,不可避免的需要使用静态资源(浏览器看的懂,他可以有变量,例:HTML页面,css样式文件,文本,属性文件,图片等): 并且SpringBoot内置了Thymeleaf ...
- 由ping所引发的思考~
今天看了掘金一片关于ping原理的文章,https://juejin.im/entry/5af8d5e651882565bd25581c?utm_source=gold_browser_extensi ...
- JVM GC-----垃圾回收算法
说到Java,一定绕不开GC,尽管不是Java首创的,但Java一定是使用GC的代表.GC就是垃圾回收,更直接点说就是内存回收.是对内存进行整理,从而使内存的使用尽可能大的被复用. 一直想好好写一篇关 ...
- mysql 和 oracle 的一些小知识
有很多应用项目, 刚起步的时候用MYSQL数据库基本上能实现各种功能需求,随着应用用户的增多,数据量的增加,MYSQL渐渐地出现不堪重负的情况:连接很慢甚至宕机,于是就有把数据从MYSQL迁到ORAC ...
- 深入浅出 TCP/IP 协议
TCP/IP 协议栈是一系列网络协议的总和,是构成网络通信的核心骨架,它定义了电子设备如何连入因特网,以及数据如何在它们之间进行传输.TCP/IP 协议采用4层结构,分别是应用层.传输层.网络层和链路 ...