c# 基于委托的异步编程模型(APM)测试用例
很多时候,我们需要程序在执行某个操作完成时,我们能够知道,以便进行下一步操作。
但是在使用原生线程或者线程池进行异步编程,没有一个内建的机制让你知道操作什么时候完成,为了克服这些限制,基于委托的异步编程模型应运而生。
通过定义回调函数能够实现异步编程,委托是一个工具,类似语c++的函数指针,当我们在使用委托时。可以传入一个符合其定义的方法。从编译器的层面看,定义一个委托,相当于定义了一个类,该类拥有一个委托链,还有三个方法,Invoke用于同步调用,而BeginInvoke和EndInvoke则是用于异步调用。
先来对这三个方法进行说明:
Invoke方法的输入输出和委托本身相同。注意:Invoke方法的主要功能就是帮助你在UI线程上调用委托所指定的方法。Invoke方法首先检查发出调用的线程(即当前线程)是不是UI线程,如果是,直接执行委托指向的方法,如果不是,它将切换到UI线程,然后执行委托指向的方法。不管当前线程是不是UI线程,Invoke都阻塞直到委托指向的方法执行完毕,然后切换回发出调用的线程(如果需要的话),返回。
BeginInvoke的输出是IAsyncResult,输入除了委托本身的输入,还包括一个回调函数AsyncCallBack,以及包括了一个object的类型参数,允许我们向异步委托传递任何类型的信息。如果把一个回调函数传入BeginInvoke,它会在委托运行完成后自动执行。
EndInvoke方法的输入总是IAsyncResult,输出则是和委托本身的输出相同。如果调用EndInvoke时,IAsyncResult对象表示的异步操作还未完成,则EndInvoke将在异步操作未完成之前阻塞调用线程(非常重要)。
如何理解这些方法的输入输出?我们可以把BeginInvoke 作为调用的开始,当调用完时,我们希望有一个回调函数,可以在希望的时候调用,从而让异步方法主动通知我们自己以及完成。so BeginInvoke的输入除了委托本身的输入外,还需要一个回到函数AsyncCallBack(当然也可以不写这个函数,传入null,这将失去主动通知的好处)。另外一个object类型的state参数,允许我们向异步委托传输任意类型的信息。这个参数,我们能在回调函数中获取到,这更加方便实现各种业务逻辑。
示例1:获取异步委托的执行结果
我们先将回调函数和状态设为null,通过EndInvoke异步委托获取目标函数的返回值,和Threading的方法相比,我们可以在委托目标的函数中设定返回值的类型,使获取结果容易了很多,但是这样会造成阻塞。因为EndInvoke强制获取结果,所以结果在没有计算出来之前,代码是无法向前进行的。这和Threading加轮询的方法类型,只是现在线程由线程池管理。
class Program
{
public delegate bool IsPrimeSlowDelegate(int number); public static bool IsPrimeSlow(int number)
{
bool b = false;
if (number <= )
{
throw new Exception("参数必须大于0");
}
if (number == )
{
throw new Exception("1既不是质数也不是合数");
}
for (int i = ; i <= number; i++)
{
Thread.Sleep();
if (number % i == )
{
b = false;
}
else
{
b = true;
} }
return b; } static void Main(string[] args)
{
IsPrimeSlowDelegate slowDelegate = new IsPrimeSlowDelegate(IsPrimeSlow);
Console.WriteLine("开始执行:" + DateTime.Now.ToString());
var ar = slowDelegate.BeginInvoke(,null,null); var er = slowDelegate.EndInvoke(ar);
Console.WriteLine("结束执行:" + DateTime.Now.ToString());
Console.ReadKey(); }
}
执行结果:(在执行时,EndInvoke一直在等待计算结果,阻塞了主线程12秒,实际上和同步调用无异,因此,我们应该使用回调函数)

BeginInvoke和EndInvoke是由一个IAsyncResult接口对象联系在一起
System.IAsyncResult接口包括:
1、一个object类型的AsyncState,存储主线程传来的的信息。
2、一个布尔类型,IsCompleted,当其为真,异步委托执行完毕。
3、一个类型为WaitHandle的属性AsyncWaitHandle.WaitHandle有个方法WaitOne,可以指定最长等待时间。
示例:
static void Main(string[] args)
{
IsPrimeSlowDelegate slowDelegate = new IsPrimeSlowDelegate(IsPrimeSlow);
Console.WriteLine("开始执行:" + DateTime.Now.ToString());
IAsyncResult ar = slowDelegate.BeginInvoke(,null,null);
//设置线程最长等待时间为3秒
bool b = ar.AsyncWaitHandle.WaitOne();
if (b)
{
//三秒后,如果当前实例收到信号,则为 true;否则为 false。当没有收到信息,就跳过执行EndInvoke
var er = slowDelegate.EndInvoke(ar);
} Console.WriteLine("结束执行:" + DateTime.Now.ToString());
Console.ReadKey(); }
示例2:通过回调函数的方式获得异步委托的执行结果
回调函数的作用是当委托完成后,可以主动通过主线程自己已经完成。我们可以在BeginInvoke中定义回调函数,这将在委托完成后自动执行,也就是说,线程将执行完回调函数才回到线程池,而不是直接回到池子中。
回到函数的类型是AsyncCallBack,其也是一个委托,传入参数必须是IAsyncResult,而且没有返回值,那么我们怎么获取返回值呢?
此时,我们可以通过回调函数的传入参数IAsyncResult来做。我们把参数显示转换为AsyncResult的形式。AsyncResult实现了IAsyncResult,它的属性AsyncDelegate是object类型的,可以指向其他地方对象的引用。此时我们可以在回调函数中建立一个委托,令其类型和main中建立的委托相同,然后,将其赋值给AsyncDelegatee(需要转换)。这时,该委托就和Main中的那个毫无二致。现在我们可以调用EndInvoke获取结果了。而且这次调用不会阻塞,代码如下
static void Main(string[] args)
{ IsPrimeSlowDelegate slowDelegate = new IsPrimeSlowDelegate(IsPrimeSlow);
var callBack = new AsyncCallback(CallBack); Console.WriteLine("开始执行:" + DateTime.Now.ToString());
slowDelegate.BeginInvoke(,cancelltion.Token,callBack,"我是最后一个参数"); Console.WriteLine("结束执行:" + DateTime.Now.ToString());
Console.Read(); } public delegate bool IsPrimeSlowDelegate(int number); public static bool IsPrimeSlow(int number)
{
bool b = false;
if (number <= )
{
throw new Exception("参数必须大于0");
}
if (number == )
{
throw new Exception("1既不是质数也不是合数");
}
for (int i = ; i <= number;i++)
{
Thread.Sleep();
if (number%i == )
{
b = false;
}
else
{
b = true;
}
}
return b;
} /// <summary>
/// 回调函数
/// </summary>
/// <returns></returns>
public static void CallBack(IAsyncResult iar)
{
var ar = (AsyncResult)iar;
var br = (IsPrimeSlowDelegate)ar.AsyncDelegate;
try
{
var re = br.EndInvoke(iar);
Console.WriteLine(re.ToString());
}
catch (Exception exp)
{
Console.WriteLine(exp.Message);
} }

主线程立即执行完成,回调函数在计算完成后自动调用。
回调函数的参数对象:

这就是真正的异步了,主线程拍完任务后还可以做其他事,而子线程在任务完成后执行回调函数。
主线程还可以向子线程传输任何类型的自定义数据,这通过BeginInvoke的最后一个参数实现。由于它是object类型,所以任何类型都可以传输。在子线程中,我们通过调用IAsyncResult的AsyncState属性获取主线程传来的数据。该示例中,红圈即为传递的参数
示例3:使用线程统一取消模型进行取消
使用委托的异步编程模型也可以使用线程统一取消模型进行取消。首先,我们需要为为委托和委托目标方法加入CancellationToken输入参数(必须修改委托定义才行,因为BeginInvoke不支持传入CancellationToken)。
然后在委托目标方法中,调用ThrowIfCancellationRequested(在循环中,保持一直监听),最后,需要在回到函数中加入try-catch,因为BeginInvoke不抛OperationCanceledException异常,只有EndInvoke才会。为了不阻塞主线程,回调函数获取结果的EndInvoke是唯一 的选择。代码如下
static void Main(string[] args)
{
//创建取消多线程的对象
CancellationTokenSource cancelltion = new CancellationTokenSource(); IsPrimeSlowDelegate slowDelegate = new IsPrimeSlowDelegate(IsPrimeSlow);
var callBack = new AsyncCallback(CallBack); Console.WriteLine("开始执行:" + DateTime.Now.ToString());
slowDelegate.BeginInvoke(,cancelltion.Token,callBack,"我是最后一个参数"); Console.WriteLine("结束执行:" + DateTime.Now.ToString());
Console.ReadKey();
Console.WriteLine("线程取消执行:"+DateTime.Now.ToString());
cancelltion.Cancel();
Console.Read(); } public delegate bool IsPrimeSlowDelegate(int number,CancellationToken token); public static bool IsPrimeSlow(int number,CancellationToken token)
{
bool b = false;
if (number <= )
{
throw new Exception("参数必须大于0");
}
if (number == )
{
throw new Exception("1既不是质数也不是合数");
}
for (int i = ; i <= number;i++)
{
Thread.Sleep();
token.ThrowIfCancellationRequested();
if (number%i == )
{
b = false;
}
else
{
b = true;
}
}
return b;
} /// <summary>
/// 回调函数
/// </summary>
/// <returns></returns>
public static void CallBack(IAsyncResult iar)
{
var ar = (AsyncResult)iar;
var br = (IsPrimeSlowDelegate)ar.AsyncDelegate;
try
{
var re = br.EndInvoke(iar);
Console.WriteLine(re.ToString());
}
catch (OperationCanceledException cancelExp)
{
Console.WriteLine("任务取消");
}
catch (Exception exp)
{
Console.WriteLine(exp.Message);
} }
取消执行回调函数中,EndInvoke抛出异常:

注意:用户在任务完成之前取消了,会抛出异常,任务完成后取消,并不会抛出异常
即使提前取消了任务,也会调用回调函数,抛出异常。
无论任务成功完成还是被取消,IAsyncResult对象的Is'Com'p'leted属性都是true;
如果委托传入小于0,和1的数据,也会抛出异常,为了捕获此类以及其他非取消线程异常。(使用catch(Exception exp)进行捕获)
c# 基于委托的异步编程模型(APM)测试用例的更多相关文章
- 转:[你必须知道的异步编程]——异步编程模型(APM)
本专题概要: 引言 你知道APM吗? 你想知道如何使用异步编程模型编写代码吗? 使用委托也可以实现异步编程,你知道否? 小结 一.引言 在前面的C#基础知识系列中介绍了从C#1.0——C#4.0中一些 ...
- [你必须知道的异步编程]——异步编程模型(APM)
本专题概要: 引言 你知道APM吗? 你想知道如何使用异步编程模型编写代码吗? 使用委托也可以实现异步编程,你知道否? 小结 一.引言 在前面的C#基础知识系列中 介绍了从C#1.0——C#4.0中一 ...
- 一、异步编程模型(APM)
一.概念 APM即异步编程模式的简写(Asynchronous Programming Model).大家在写代码的时候或者查看.NET 的类库的时候肯定会经常看到和使用以BeginXXX和EndXX ...
- 异步编程模型(APM)模式
什么是APM .net 1.0时期就提出的一种异步模式,并且基于IAsyncResult接口实现BeginXXX和EndXXX类似的方法. .net中有很多类实现了该模式(比如HttpWebReque ...
- 异步编程:IAsyncResult异步编程模型 (APM)
http://www.cnblogs.com/heyuquan/archive/2013/03/22/2976420.html
- C#异步编程の-------异步编程模型(APM)
术语解释: APM 异步编程模型, Asynchronous Programming Model EAP 基于事件的异步编程模式, Event ...
- .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)
本文内容 异步编程类型 异步编程模型(APM) 参考资料 首先澄清,异步编程模式(Asynchronous Programming Patterns)与异步编程模型(Asynchronous Prog ...
- 二、基于事件的异步编程模式(EAP)
一.引言 在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式--APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题--不支持对异步操作的取消和没有提供对进 ...
- 异步编程(二)基于事件的异步编程模式 (EAP)
一.引言 在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式——APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题——不支持对异步操作的取消和没有提供对进 ...
随机推荐
- Java操作Excel中HSSFCell.CELL_TYPE_STRING、BOOLEAN、NUMERIC无定义解决方法
错误原因:jar包版本更新,官方改动: 解决方法: 导入CellType包import org.apache.poi.ss.usermodel.CellType使用CellType.STRING代替H ...
- MongoDB 高级查询_aggregate聚合管道
MongoDB 聚合管道(AggregationPipeline) 使用聚合管道可以对集合中的文档进行变换和组合.实际项目应用主要是表关联查询.数据的统计. MongoDB 中使用 db.COLLEC ...
- 【2019.11.13】SDN上机第3次作业
参考资料:https://www.cnblogs.com/fjlinww/p/11834092.html 实验一 利用Mininet仿真平台构建如下图所示的网络拓扑,配置主机h1和h2的IP地址(h1 ...
- hdu1237 简单计算器[STL 栈]
目录 题目地址 题干 代码和解释 参考 题目地址 hdu1237 题干 代码和解释 解本题时使用了STL 栈,要记得使用#include<stack>. 解本题时使用了isdigit()函 ...
- Unity3D新手入门初级教程
根据游戏调查公司 Newzoo 针对全球手机游戏市场所做的调查报告显示,2016年全球游戏市场规模将达到 996 亿美元,其中手机游戏市场将以 21.3% 的增幅获得约 369 亿美元的收入,而中国手 ...
- CentOS下启动和停止Tomcat
启动Tomcat: 进入tomcat目录/bin,然后./startup.sh 停止Tomcat: 进入tomcat目录/bin,然后./shutdown.sh
- Oracle定时任务执行存储过程备份日志记录表
写在前面 需求 1.备份系统日志表T_S_LOG, 按照操作时间字段OPERATETIME, 将每天的日志增量备份到另一张表. 思路 1.创建一张数据结构完全相同的表T_S_LOG_BAK作为备份表 ...
- 腾讯云短信 nodejs 接入, 通过验证码修改手机示例
腾讯云短信 nodejs 接入, 通过验证码修改手机示例 参考:腾讯云短信文档国内短信快速入门qcloudsms Node.js SDK文档中心>短信>错误码 nodejs sdk 使用示 ...
- [Bayes] MCMC (Markov Chain Monte Carlo)
不错的文章:LDA-math-MCMC 和 Gibbs Sampling 可作为精进MCMC抽样方法的学习材料. 简单概率分布的模拟 Box-Muller变换原理详解 本质上来说,计算机只能生产符合均 ...
- 【linux学习笔记六】压缩 解压缩命令
所有的压缩文件一定要写压缩格式的扩展名 .zip格式压缩 #压缩文件 zip 压缩文件名 源文件 #压缩目录 zip -r 压缩文件名 源目录 #解压缩 unzip 压缩文件 .gz格式压缩 #压缩为 ...