.Net组件程序设计之异步调用

说到异步调用,在脑海中首先想到就是BeginInvoke(),在一些常用对象中我们也会常常见到Invoke()和BeginInvoke(), 要想让自己的组件可以被客户端调用或者是异步调用,这样的设计是合理的,这也是组件异步机制当中的一条 (说句题外话--其实大多数知识都隐藏在我们平时经常见到的对象或者是代码里,只不过是没有去细心的发现) 在.NET中首先就会想到使用委托来进行异步调用,关于委托的定义在 委托与事件一文中已经大概的说过了,文中只是对委托进行了 大概的讲解,并没有对委托的使用来说明或者是例举一些示例。 在本篇中将会对委托进行一个基础的揭底,主要方向是异步调用。

一 委托的老调重弹

     public class Operation
{
public int Addition(int num1, int num2)
{
return num1 + num2;
}
public int Subtraction(int num1, int num2)
{
return num1 - num2;
}
}

没有必要直接使用Operation对象来进行加减运算,可以使用委托:

 public delegate int OperationDelegate(int num1, int num2);

 Operation operation = new Operation();
OperationDelegate Additiondelegate = operation.Addition; int result;
result = Additiondelegate.Invoke(, );
Debug.Assert(result == );

在使用委托进行调用的时候,当前线程是被阻塞的,只有当委托执行完毕了,才会把控制权交回到当前线程。
不过呢,委托可以用于进行异步调用目标方法的,委托只是一种特定的类型,编译器会把我们定义的各式各样的委托编译成
对应的类,好比OperationDelegate委托一样,实则是被编译成这样的

     public sealed class OperationDelegate : MulticastDelegate
{
public OperationDelegate(Object target, int methodPtr) { }
public virtual Invoke(int num1,int num2)
{
……
} public virtual IAsyncResult BeginInvoke(int num1,int num2,AsyncCallback callback,object asyncState)
{
……
} public virtual int EndInvoke(IAsyncResult result)
{
……
}
}

这里只是回顾一下委托的定义。

二 异步调用编程模型

图1

在上图我们所见的有这几个模块, .NET线程池、异步调用请求队列和一个应用程序的主线程。
假使现在从任务1开始执行到任务2、任务3,到了任务3的时候,任务3请求.NET执行异步操作,如图2

图2

这个时候【任务3】已经被送入到了【异步请求队列】中,并且主线程是阻塞状态的,再看图3的执行过程:

图3

线程池会及时的发现【异步请求队列】中的任务,并且根据任务的信息,线程池会分配一个线程到任务所在的主线程中执行所请求的任务。 在异步任务执行时,这个时候主线程才会从阻塞中撤销,进入执行状态,上图中,就是开始执行任务4。

这里要说的就是,异步调用看起来是并行执行的,实际刚开始的时候还是顺序的,不过这时间在实际情况中是忽略不计的, 可以认为就是并行执行的吧。

 三 BeginInvoke()、EndInvoke()

3.1 BeginInvoke()

BeginInvoke()函数定义如下:

 public virtual IAsyncResult BeginInvoke(int num1,int num2,AsyncCallback callback,object asyncState)
{
……
}

接受OperationDelegate委托定义的原始签名的输入参数,还有两个额外参数,AsyncCallback是系统定义的委托, 用于异步调用完成时回调所用,这里不做讲解,后面会有讲到,还有一个是参数是一个状态对象,也可以认为是容器对象, 也会在后面的章节中讲到。

 Operation operation = new Operation();
OperationDelegate Additiondelegate = operation.Addition;
Additiondelegate.BeginInvoke(, , null, null);

3.2 IAsyncResult接口

正如上面所看到的,BeginInvoke函数返回一个IAsyncResult类型的值,那就来看一下IAsyncResult的定义:

     public interface IAsyncResult
{
object AsyncState { get; }
WaitHandle AsyncWaitHandle { get; }
bool CompletedSynchronously { get; }
bool IsCompleted { get; }
}

对于IAsyncResult的详细用法 稍后会有讲解

看到第一节的Invoke函数执行后,可以直接获取到返回值,怎么这个BeginInvoke函数执行了返回

IAsyncResult类型,返回值在哪呢? 可以通过从BeginInvoke函数获得的IAsyncResult交给EndInvoke函数来获取返回值。

 Operation operation = new Operation();
OperationDelegate Additiondelegate = operation.Addition; IAsyncResult asyncResult = Additiondelegate.BeginInvoke(, , null, null);
int result = Additiondelegate.EndInvoke(asyncResult);
Debug.Assert(result == );

这里要说几点

第一.调用EndInvoke函数的时候,当前线程是被阻塞的,它在等待BeginInvoke函数执行完毕。

第二.虽然委托可以管理多个目标方法,但是在异步调用中,所执行异步调用的委托,内部的管理列表只能有一个目标方法,不然会报 有异常。

第三.EndInvoke()在每次异步调用操作时 只能调用一次。

第四.BeginInvoke()返回的IAsyncResult类型的实例,只能传入它所调用BeginInvoke()委托的EndInvoke()中,不然也会报有异常。

3.3 AsyncResult

假使一个客户端在一个代码段或者是函数中使用BeginInvoke(),而在另一段或者是其他的函数中调用EndInvoke(),这样客户端是不是就要保存IAsyncResult对象,又或者一个客户端发起异步调用,并且由另一个 客户端来调用EndInvoke(),这不仅仅要保存IAsyncResult对象,还需要保存该委托对象,而且你还得传送过去。 还好.NET是那么的机智,有System.Runtime.Remoting.Messaging.AsyncResult类型的存在。

    public class AsyncResult : IAsyncResult, IMessageSink
{ #region IAsyncResult 成员 public object AsyncState
{
get { throw new NotImplementedException(); }
} public System.Threading.WaitHandle AsyncWaitHandle
{
get { throw new NotImplementedException(); }
} public bool CompletedSynchronously
{
get { throw new NotImplementedException(); }
} public bool IsCompleted
{
get { throw new NotImplementedException(); }
} #endregion public bool EndInvokeCalled { get; set; } public virtual object AsyncDelegate { get; } //IMessageSink 成员
}

看着上面有个AsyncDelegate的属性,会不会觉得很漂亮,不错,它就是原始发起委托的引用,看下如何使用AsyncDelegate来使用EndInvoke():

     public class OperationTest
{ public void Test()
{
Operation operation = new Operation();
OperationDelegate Additiondelegate = operation.Addition;
int Result;
Result = GetResult(Additiondelegate.BeginInvoke(, , null, null));
} private int GetResult(IAsyncResult asyncresult)
{
AsyncResult asyncResult = (AsyncResult)asyncresult;
OperationDelegate operationdelegate = asyncResult.AsyncDelegate as OperationDelegate;
if (operationdelegate != null)
{
Debug.Assert(asyncResult.EndInvokeCalled == false);//EndInvoke()是否被调用过
return operationdelegate.EndInvoke(asyncResult);
}
return -;
}
}

3.4 轮循或等待

看到这里,善于思考的朋友会发现,还存在着一个很大的问题,就是发起异步调用的客户端,如何知道自己 的异步函数是否执行完毕了?或者是想等待一会,做一些其他的处理,然后再继续等待,该怎么来实现呢?

从BeginInvoke()返回的IAsyncResult接口有个AsyncWaitHandle属性,它是干吗的呢?就把它理解为消息接收器吧。

 Operation operation = new Operation();
OperationDelegate Additiondelegate = operation.Addition;
IAsyncResult asyncResult = Additiondelegate.BeginInvoke(, , null, null);
asyncResult.AsyncWaitHandle.WaitOne();//如果任务完成则不会阻塞 否则阻塞当前线程
int Result;
Result = Additiondelegate.EndInvoke(asyncResult);
Debug.Assert(Result == );

代码和3.2的几乎相同,区别就是这段代码保证了EndInvoke()的调用者不会被阻塞。

看一下等待一下,如果没完成处理其他任务,回来再等待是怎么实现的。

 Operation operation = new Operation();
OperationDelegate Additiondelegate = operation.Addition;
IAsyncResult asyncResult = Additiondelegate.BeginInvoke(, , null, null);
while (asyncResult.IsCompleted == false)//判断异步任务是否完成
{
asyncResult.AsyncWaitHandle.WaitOne(,false);//如果任务完成则不会阻塞 否则阻塞当前线程10毫秒
//这里做一些其他操作
}
int Result;
Result = Additiondelegate.EndInvoke(asyncResult);
Debug.Assert(Result == );

3.5 使用回调函数

现在我们要来说说BeginInvoke()的第三个参数了, public delegate void AsyncCallback(IAsyncResult ar);

第三个参数就是系统提供的一个委托类型,委托签名也都看到了。 使用回调函数的好处就是不需要去处理等待操作了,因为在异步任务完成的时候, 会调用你传给BeginInvoke()里AsyncCallback委托所关联的目标方法。

     public class OperationTest
{ public void Test()
{
Operation operation = new Operation();
OperationDelegate Additiondelegate = operation.Addition; Additiondelegate.BeginInvoke(, , new AsyncCallback(OnCallBack), null);
} private void OnCallBack(IAsyncResult asyncresult)
{
AsyncResult asyncResult = (AsyncResult)asyncresult;
OperationDelegate operationdelegate = asyncResult.AsyncDelegate as OperationDelegate;
if (operationdelegate != null)
{
Debug.Assert(asyncResult.EndInvokeCalled == false);
int result=operationdelegate.EndInvoke(asyncResult);
Console.WriteLine("Operation returned" + result.ToString());
}
}
}

这里需要说的是在异步任务完成时,执行的回调函数依然是在子线程当中,并不是在主线程中执行回调函数的。

题外话:最常见的就是在Winform开发中,Form中发起异步调用,然后回调函数操作Form中的控件或者是

值的时候就会报错, 就是这个原因,因为它们不在一个线程也不在一个上下文中,基于.NET安全策略这种操作是不允许的。

END

作者:金源

出处:http://www.cnblogs.com/jin-yuan/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面

.Net组件程序设计之异步调用的更多相关文章

  1. .Net组件程序设计之远程调用(二)

    .Net组件程序设计之远程调用(二) 激活模式 引用封送对象激活类型两种, 一种是客户端激活类型,一种是服务器端激活. 客户端激活对象 客户端激活方式:当客户端创建一个远程对象时,客户端得到的是一个新 ...

  2. .Net组件程序设计之远程调用(一)

    .Net组件程序设计之远程调用(一) 1应用程序域 我们知道我们写的C#代码是在操作系统逻辑体系结构中最上层的,然而操作系统本身是不会认识C#代码的,它只认识机器代码.那我们写的程序经过编译后是编译成 ...

  3. .NET组件程序设计之线程、并发管理(二)

    .Net组件程序设计之线程.并发管理(二) 2.同步线程 手动同步 监视器 互斥 可等待事件 同步线程 所有的.NET组件都支持在多线程的环境中运行,可以被多个线程并发访问,如果没有线程同步,这样的后 ...

  4. .Net组件程序设计之线程、并发管理(一)

    .Net组件程序设计之线程.并发管理(一) 1.线程 线程 线程的创建 线程的阻塞 线程挂起 线程睡眠 加入线程 线程中止 现在几乎所有的应用程序都是多线程的,给用户看来就是一个应用程序界面(应用程序 ...

  5. .Net组件程序设计之序列化

     .Net组件程序设计之序列化 自动序列化 本篇给大家讲解一下在.NET中的序列化,它的用处非常之多,特别是对于某种环境下保存某种状态是很好的方法,接下来就来看一下吧. Serializable属性 ...

  6. .Net组件程序设计之上下文

    .Net组件程序设计之上下文 在后续篇幅的远程调用的文章里有说到应用程序域,那是大粒度的控制程序集的逻辑存在,那么想对对象的控制又由谁来做主呢?没错了,就是上下文.CLR把应用程序域更细化了,在应用程 ...

  7. .Net组件程序设计之对象生命周期

    .Net组件程序设计之对象生命周期 .NET 垃圾回收 IDisposable() Using语句 .NET 垃圾回收 是CLR管理着垃圾回收器,垃圾回收器监控着托管堆,而我们使用的对象以及系统启动是 ...

  8. .Net组件程序设计

    .Net组件程序设计之上下文 在后续篇幅的远程调用的文章里有说到应用程序域,那是大粒度的控制程序集的逻辑存在,那么想对对象的控制又由谁来做主呢?没错了,就是上下文.CLR把应用程序域更细化了,在应用程 ...

  9. SpringBoot中异步请求和异步调用(看这一篇就够了)

    原创不易,如需转载,请注明出处https://www.cnblogs.com/baixianlong/p/10661591.html,否则将追究法律责任!!! 一.SpringBoot中异步请求的使用 ...

随机推荐

  1. echarts之tooltip

    tooltip:提示框,鼠标悬浮交互时的信息提示. 当trigger为'item'时 tooltip : { trigger: 'item' }, 当trigger为'axis'时 tooltip : ...

  2. selenium使用笔记(二)——Tesseract OCR

    在自动化测试过程中我们经常会遇到需要输入验证码的情况,而现在一般以图片验证码居多.通常我们处理这种情况应该用最简单的方式,让开发给个万能验证码或者直接将验证码这个环节跳过.之前在技术交流群里也跟朋友讨 ...

  3. $event 获取对象

    用Angular给元素添加事件时获取可以用 $event 传递当前触发的事件的元素对象 页面上可以这样写 <img ng-src="" alt="" ng ...

  4. 测试--jmeter的使用

    jmeter用于压力测试 首先我们要区别压力和攻击,当设立了不适当的线程数量和准备时长,就容易造成攻击. 线程数:虚拟用户数.一个虚拟用户占用一个进程或线程.设置多少虚拟用户数在这里也就是设置多少个线 ...

  5. T-SQL Recipes之Organizing and Archiving Data

    The Problem 当我们处理存档数据或内存数据时,我们想要自定义命名表名,数据库,架构加上日期,时间,或者应用名时,用标准的TSQL来实现是比较困难的. 假设我们有一张日志表,增长速度异常快.但 ...

  6. javascript系列:NaN类型

    NaN,即非数值是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况下. ECMAScript中任何数值除以0会返回NaN,因此不影响其他代码运行.   NaN特点:     ...

  7. underscorejs 源码走读笔记

    Underscore 简介 Underscore 是一个JavaScript实用库,提供了类似Prototype.js的一些功能,但是没有继承任何JavaScript内置对象.它弥补了部分jQuery ...

  8. JavaScript笔试必备语句【转】

    1. document.write( " "); 输出语句 2.JS中的注释为// 3.传统的HTML文档顺序是:document- >html- >(head,bod ...

  9. linux下memcached安装以及启动

    1. 准备安装文件 下载memcached与libevent的安装文件 http://memcached.googlecode.com/files/memcached-1.4.15.tar.gz(me ...

  10. duplicate symbols for architecture arm64 after xCode 8.0 update

    Xcode IDE  从7.3.1 update 到 8.0 之后出现的问题 一个错误把我困扰了两天之久,最终找到解决办法我欣喜若狂. 错误发生原因:Xcode IDE  从7.3.1 update ...