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

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

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

一 委托的老调重弹


 1     public class Operation
2 {
3 public int Addition(int num1, int num2)
4 {
5 return num1 + num2;
6 }
7 public int Subtraction(int num1, int num2)
8 {
9 return num1 - num2;
10 }
11 }

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


1 public delegate int OperationDelegate(int num1, int num2);
2
3 Operation operation = new Operation();
4 OperationDelegate Additiondelegate = operation.Addition;
5
6 int result;
7 result = Additiondelegate.Invoke(3, 4);
8 Debug.Assert(result == 7);

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


 1     public sealed class OperationDelegate : MulticastDelegate
2 {
3 public OperationDelegate(Object target, int methodPtr) { }
4 public virtual Invoke(int num1,int num2)
5 {
6 ……
7 }
8
9 public virtual IAsyncResult BeginInvoke(int num1,int num2,AsyncCallback
10
11 callback,object asyncState)
12 {
13 ……
14 }
15
16 public virtual int EndInvoke(IAsyncResult result)
17 {
18 ……
19 }
20 }

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

二 异步调用编程模型

图1

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

图2

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

图3

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

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

 三 BeginInvoke()、EndInvoke()

3.1 BeginInvoke()

BeginInvoke()函数定义如下:


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

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


1 Operation operation = new Operation();
2 OperationDelegate Additiondelegate = operation.Addition;
3 Additiondelegate.BeginInvoke(3, 4, null, null);

3.2 IAsyncResult接口

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


1     public interface IAsyncResult
2 {
3 object AsyncState { get; }
4 WaitHandle AsyncWaitHandle { get; }
5 bool CompletedSynchronously { get; }
6 bool IsCompleted { get; }
7 }

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

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

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


1 Operation operation = new Operation();
2 OperationDelegate Additiondelegate = operation.Addition;
3
4 IAsyncResult asyncResult = Additiondelegate.BeginInvoke(3, 4, null, null);
5 int result = Additiondelegate.EndInvoke(asyncResult);
6 Debug.Assert(result == 7);

这里要说几点

第一.调用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():


 1     public class OperationTest
2 {
3
4 public void Test()
5 {
6 Operation operation = new Operation();
7 OperationDelegate Additiondelegate = operation.Addition;
8 int Result;
9 Result = GetResult(Additiondelegate.BeginInvoke(3, 4, null, null));
10 }
11
12 private int GetResult(IAsyncResult asyncresult)
13 {
14 AsyncResult asyncResult = (AsyncResult)asyncresult;
15 OperationDelegate operationdelegate = asyncResult.AsyncDelegate as
16
17 OperationDelegate;
18 if (operationdelegate != null)
19 {
20 Debug.Assert(asyncResult.EndInvokeCalled == false);//EndInvoke()是否被调用过
21 return operationdelegate.EndInvoke(asyncResult);
22 }
23 return -1;
24 }
25 }

3.4 轮循或等待

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

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


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

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

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


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

3.5 使用回调函数

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

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


 1     public class OperationTest
2 {
3
4 public void Test()
5 {
6 Operation operation = new Operation();
7 OperationDelegate Additiondelegate = operation.Addition;
8
9 Additiondelegate.BeginInvoke(2, 3, new AsyncCallback(OnCallBack), null);
10 }
11
12 private void OnCallBack(IAsyncResult asyncresult)
13 {
14 AsyncResult asyncResult = (AsyncResult)asyncresult;
15 OperationDelegate operationdelegate = asyncResult.AsyncDelegate as
16
17 OperationDelegate;
18 if (operationdelegate != null)
19 {
20 Debug.Assert(asyncResult.EndInvokeCalled == false);
21 int result=operationdelegate.EndInvoke(asyncResult);
22 Console.WriteLine("Operation returned" + result.ToString());
23 }
24 }
25 }

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

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

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

END

作者:金源

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

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

时间: 2025-01-08 01:38:36

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

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

.Net组件程序设计之远程调用(一) 1应用程序域 我们知道我们写的C#代码是在操作系统逻辑体系结构中最上层的,然而操作系统本身是不会认识C#代码的,它只认识机器代码.那我们写的程序经过编译后是编译成IL的,是怎么运行的呢?实际是在一个托管的环境下运行的,是.NET提供的支持,操作系统是不会识别IL的,这中间就需要一个桥梁:应用程序域.操作系统中的进程是资源单位,应用程序域的执行使用当然也要占用空间使用资源了,所以是物理进程承载着应用程序域的,而且这种承载关系并不是一对一的. 图:应用程序域 使

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

position:static(静态定位) 当position属性定义为static时,可以将元素定义为静态位置,所谓静态位置就是各个元素在HTML文档流中应有的位置 podisition定位问题.所以当没有定义position属性时,并不说明该元素没有自己的位置,它会遵循默认显示为静态位置,在静态定位状态下无法通过坐标值(top,left,right,bottom)来改变它的位置. position:absolute(绝对定位) 当position属性定义为absolute时,元素会脱离文档流

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

.Net组件程序设计之线程.并发管理(二) 2.同步线程 手动同步 监视器 互斥 可等待事件 同步线程 所有的.NET组件都支持在多线程的环境中运行,可以被多个线程并发访问,如果没有线程同步,这样的后果是当多个线程同时访问 对象状态时,对象的状态可能被破坏,造成不一致性..NET提供了两种方法来避免这样的问题,使得我们设计的组件更加健壮. 第一种是自动同步,让你使用一个属性来修饰组件,这样就可以把组件交给.NET了,同步的事情也就交给了.NET. 第二种是手动同步,这是让你使用.NET提供的同步

一位牛人的多线程和异步调用文章

转自小顾问原文 一位牛人的多线程和异步调用文章 首先申明:这篇文章不是我写的,我看到的一位牛人的,自己慢慢的消化了…… 摘要:本章讨论与智能客户端应用程序中多线程的使用有关的问题.为了最大限度地提高智能客户端应用程序的响应能力,需要仔细考虑如何和何时使用多线程.线程可以大大提高应用程序的可用性和性能,但是当您确定它们将如何与用户界面交互时,需要对其进行非常仔细的考虑. 线程是基本执行单元.单线程执行一系列应用程序指令,并且在应用程序中从头到尾都经由单一的逻辑路径.所有的应用程序都至少有一个线程,

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

.Net组件程序设计之线程.并发管理(一) 1.线程 线程 线程的创建 线程的阻塞 线程挂起 线程睡眠 加入线程 线程中止 现在几乎所有的应用程序都是多线程的,给用户看来就是一个应用程序界面(应用程序线程),不管什么操作都不会导致界面出现响应慢的情况,这些都是多线程的功劳,有了多线程,可以让应用程序尽最大可能的处理更多的操作,调动很多线程来并行处理请求,这样会使得应用程序有更大的系统吞吐量. 1.线程 1.1线程 线程是什么呢?线程就是进程中的一条执行路径,每个应用程序至少在一个线程上运行.在本

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

.Net组件程序设计之对象生命周期 .NET 垃圾回收 IDisposable() Using语句 .NET 垃圾回收 是CLR管理着垃圾回收器,垃圾回收器监控着托管堆,而我们使用的对象以及系统启动是所需要的一些必备的对象信息都存在于托管堆上,CLR会维护着一个列表(对象引用信息列表). 这个列表里存放的信息就是对应着托管堆中所有对象的信息(引用.被引用信息)每当使用一个新的对象或者是改变一个现有对象的引用CLR都会更新 对象引用信息列表.那么回收器一般什么时候调用呢? 垃圾回收器大多数是在托管

使用RxBinding处理控件异步调用

欢迎Follow我的GitHub, 关注我的CSDN. RxBinding是Rx中处理控件异步调用的方式, 也是由Square公司开发, Jake负责编写. 通过绑定组件, 异步获取事件, 并进行处理. 编码风格非常优雅. 让我来讲解一下如何使用, 本文含有代码示例. Github下载, 关注RxBinding部分, 其余参考. 1. 依赖 除了RxJava, 再添加RxBinding的依赖. // RxBinding compile 'com.jakewharton.rxbinding:rxb

C#中的异步调用及异步设计模式(三)——基于事件的异步模式

四.基于事件的异步模式(设计层面) 基于事件的C#异步编程模式是比IAsyncResult模式更高级的一种异步编程模式,也被用在更多的场合.该异步模式具有以下优点: ·                  “在后台”执行耗时任务(例如下载和数据库操作),但不会中断您的应用程序. ·                  同时执行多个操作,每个操作完成时都会接到通知(在通知中可以区分是完成了哪个操作). ·                  等待资源变得可用,但不会停止(“挂起”)您的应用程序. ·  

客户端的异步调用

C#客户端的异步操作 阅读目录 开始 示例项目介绍 同步调用服务 异步接口介绍 1. 委托异步调用 2. 使用IAsyncResult接口实现异步调用 3. 基于事件的异步调用模式 4. 创建新线程的异步方式 5. 使用线程池的异步方式 6. 使用BackgroundWorker实现异步调用 客户端的其它代码 各种异步方式的优缺点 异步文件I/O操作 数据库的异步操作 异步设计的使用总结 在Asp.net中使用异步 上篇博客[用Asp.net写自己的服务框架] 我讲述了如何实现自己的服务框架,但