前言

在.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来对文件进行异步字节读取操作。

使用

在程序运行过程中,直接调用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获取结果,那么会发生什么呢?

如下图所示:

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

原因是这样的: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(3000); //模拟耗时操作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(3000); //模拟耗时操作Console.WriteLine("我是异步输出,我的名字是:" + Thread.CurrentThread.Name);}public static void CallBack(IAsyncResult ar){//使用IAsyncResult的AsyncState获取BeginInvoke中的参数,并用于执行EndInvokeConsoleDelegate callBackDelegate = ar.AsyncState as ConsoleDelegate;callBackDelegate.EndInvoke(ar);}}
}

运行后结果如下:

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

小结

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

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

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

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

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

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

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

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

公众号:wacky的碎碎念

知乎:wacky

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

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

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

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

    前言 在上一篇博文中,我们提到了APM模型实现异步编程的模式,通过使用APM模型,可以简化.Net中编写异步程序的方式,但APM模型本身依然存在一些缺点,如无法得知操作进度,不能取消异步操作等. 针对 ...

  3. 浅谈.Net异步编程的前世今生----异步函数篇(完结)

    前言 上一篇我们着重讲解了TPL任务并行库,可以看出TPL已经很符合现代API的特性:简洁易用.但它的不足之处在于,使用者难以理解程序的实际执行顺序. 为了解决这些问题,在C# 5.0中,引入了新的语 ...

  4. java 异步_浅谈Java异步编程

    本文来自网易云社区. Java异步编程引言 Java的异步编程其实是一个充分利用计算机CPU资源,不想让主程序阻塞在某个长时间运行的任务上,这类耗时的任务可以是IO操作.远程调用以及高密度计算任务.如 ...

  5. python编写函数_浅谈Python 函数式编程

    匿名函数lambda表达式 什么是匿名函数? 匿名函数,顾名思义就是没有名字的函数,在程序中不用使用 def 进行定义,可以直接使用 lambda 关键字编写简单的代码逻辑.lambda 本质上是一个 ...

  6. python采用函数编程模式_浅谈Python 函数式编程

    匿名函数lambda表达式 什么是匿名函数? 匿名函数,顾名思义就是没有名字的函数,在程序中不用使用 def 进行定义,可以直接使用 lambda 关键字编写简单的代码逻辑.lambda 本质上是一个 ...

  7. python采用函数式编程模式-浅谈Python 函数式编程

    匿名函数lambda表达式 什么是匿名函数? 匿名函数,顾名思义就是没有名字的函数,在程序中不用使用 def 进行定义,可以直接使用 lambda 关键字编写简单的代码逻辑.lambda 本质上是一个 ...

  8. C# 线程知识--异步编程模型(APM)

    在构建高性能.可伸缩的应用程序时,必定会采用异步操作来提升应用程序性能,改善用户体验,异步操作与线程池结合允许使用很少的线程执行许多的操作.CLR中提供了一种异步操作的模式即异步编程模式. 1.异步编 ...

  9. 浅谈对java编程思想的理解

    浅谈对java编程思想的理解 刚从学校毕业的时候,对于这种概念的理解少之又少 ,只是单纯的从事编码工作,理解也只是停留在对java基本概念的使用上,很局限!随后工作了快三年的时间里,自己不断的理解这种 ...

最新文章

  1. 北京计算机学院 肖战,Shuai Li
  2. 页面乱码问题的解决方案
  3. k8s ingress
  4. mysql 5.7.21 主从_Mysql 5.7.21 设置主从库同步
  5. ssl1056-金明的预算方案【dp之有依赖的背包】
  6. 大数据技术 学习之旅_如何开始您的数据科学之旅?
  7. msgpack java lua_使用lua-cmsgpack序列化和反序列化lua对象
  8. Python机器学习:线型回归法01简单线型回归法
  9. python限制输入数字范围_关于python:如何限制Django模型中数字字段的最大值?
  10. torch--[Pytorch函数] .masked_fill_() ;关于pytorch中@和*的用处
  11. u盘如何修复 新萝卜U盘官方网站原创
  12. php学习五:数组操作
  13. json字符串数组转json数组
  14. 基于鱼群算法的函数寻优算法
  15. win10非核心版本的计算机上
  16. java开发随记之 Invalidate Caches / Restart
  17. 机器学习算法——神经网络3(误差逆传播算法-BP算法)
  18. matlab的零极点分布图,matlab零极点分布图
  19. 等比矩阵求和-POJ3233
  20. python显示gif图片_利用Python制作GIF图片

热门文章

  1. Spring入门5.事务管理机制
  2. 认识Linux下的各种系统服务
  3. Web应用性能分析工具—HAR文件
  4. Discrete Log Algorithms :Baby-step giant-step
  5. Python 循环删除指定文件夹下所有的.longtian类型文件
  6. E20171214-sl
  7. JDBC学习笔记之JDBC简介
  8. 纳税服务系统【角色与用户】
  9. Koa -- 基于 Node.js 平台的下一代 web 开发框架
  10. Oracle数据库案例整理-Oracle系统执行时故障-Shared Pool内存不足导致数据库响应缓慢...