很多时候,我们需要程序在执行某个操作完成时,我们能够知道,以便进行下一步操作。

但是在使用原生线程或者线程池进行异步编程,没有一个内建的机制让你知道操作什么时候完成,为了克服这些限制,基于委托的异步编程模型应运而生。

通过定义回调函数能够实现异步编程,委托是一个工具,类似语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)进行捕获)

最新文章

  1. 解决服务器连接错误Host ‘XXX’ is not allowed to connect to this MySQL server
  2. 微软职位内部推荐-ATG Engineer II
  3. bzoj 2693: jzptab 线性筛积性函数
  4. Path对象
  5. POJ 2348 Euclid's Game(简单博弈)
  6. Java设计模式面试题 01 - 六大原则
  7. riakKV 配置
  8. SQLServer 2008 已成功与服务器建立连接,但是在登录前的握手期间发生错误。 (provider: SSL Provider, error: 0 - 等待的操作过时。
  9. BackgroundWorker 组件 -- 进度条
  10. c# 判断当前版本是Debugger或Release
  11. JavaScript基础——深入学习async/await
  12. Flutter在Windows平台下的安装配置
  13. Android 百度sdk5.0定位
  14. 如何查看一个进程打开哪些fd及对应的文件或套接字操作
  15. JAVA多线程------用1
  16. leetcode个人题解——two sum
  17. svn服务器端回退版本 (转)
  18. CSS实战2
  19. Python字符串相关
  20. Maven学习----dependencies与dependencyManagement的区别(转)

热门文章

  1. 关于html异步加载外部json文件报错问题
  2. mysql 日期处理
  3. [Beta]第三次 Scrum Meeting
  4. 第07组 Beta冲刺(4/5)
  5. node 部署教程二
  6. openresty开发系列36--openresty执行流程之6日志模块处理阶段
  7. mysql 某表某列支持 emoji
  8. Jenkins - 以Docker方式安装启动Jenkins
  9. 使用二进制的方式部署 K8S-1.16 高可用集群
  10. k8s 集群 节点状态显示notready