.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对象来进行加减运算,可使用委托:async

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     }

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

二 异步调用编程模型线程

图1设计

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

图2orm

这个时候【任务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/

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

相关文章
相关标签/搜索