• 什么是.Net的异步机制(Invoke,BeginInvoke,EndInvoke) step 2


    我们怎样进行异步编程/开发?

     

    现在扩充下上篇文章的类(AsyncTest),提供更多的例子并从中做下简单的对比, 从新的认识下异步的内部机制,下面我们增加一个新的委托

    1,我们添加一个新方法(计算年薪YearlySalary)

    public decimal YearlySalary(decimal salary, int monthCount, decimal bonus);

    2,为这个方法增加异步的功能,这样我们仍然使用委托(Delegate)

    public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus);

     

    经过简单修改后,下面是我们新的AsyncTest

    Code1

    复制代码
     1//我们使用委托来提供.Net的异步机制
     2public delegate string AsyncEventHandler(string name); // 对应Hello 方法
     3public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus); // 对应YearlySalary方法
     4public class AsyncTest
     5{
     6    public string Hello(string name)
     7    {
     8        return "Hello:" + name;
     9    }

    10
    11    /// <summary>
    12    /// 计算一年的薪水
    13    /// </summary>
    14    /// <param name="salary">月薪</param>
    15    /// <param name="monthCount">一年支付月数量</param>
    16    /// <param name="bonus">奖金</param>
    17    /// <returns></returns>

    18    public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
    19    {
    20        //添加辅助方法,查看当前的线程ID
    21        Console.WriteLine("Thread ID:#{0}", Thread.CurrentThread.ManagedThreadId);
    22
    23        return salary * monthCount + bonus;
    24    }

    25}
    复制代码

     

    这里用.NET Reflector 5 来反编译,之所以用这个,因为比微软的会更加清晰明了.如果想了解这个工具的朋友可查看(http://reflector.red-gate.com/)

    1

    开始我先对图1中的小图标进行个简单的解释

     = (Class)    = 类继承的基类  = sealed(委托)

     = 类的构造函数  = 方法  virtual方法

     

    下面我们先比较下SalaryEventHandler AsyncEventHandler委托的异同.

    1)      SalaryEventHandler

    public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus);

    2.1

    编译器生成的类Code2.1(2.1) 
    Code 2.1

    复制代码
     1    public sealed class SalaryEventHandler : MulticastDelegate
     2    {
     3        public SalaryEventHandler(object @object, IntPtr method)
     4        {.}
     5        public virtual IAsyncResult BeginInvoke(decimal salary, int monthCount, decimal bonus,
     AsyncCallback callback, 
    object @object)
     6        {}
     7        public virtual decimal EndInvoke(IAsyncResult result)
     8        {}
     9        public virtual decimal Invoke(decimal salary, int monthCount, decimal bonus)
    10        {}
    11    }
    复制代码

    2)      AsyncEventHandler

    public delegate string AsyncEventHandler(string name);

    2.2

    编译器生成的类Code2.2(2.2)

    Code2.2

    复制代码
     1    public sealed class AsyncEventHandler : MulticastDelegate
     2    {
     3        public AsyncEventHandler(object @object, IntPtr method)
     4        {.}
     5        public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object)
     6        {}
     7        public virtual string EndInvoke(IAsyncResult result)
     8        {}
     9        public virtual string Invoke(string name)
    10        {}
    11    }
    复制代码

     

    对比两个委托(事实上是一个sealed 的类),都继承于System.MuliticaseDelegate, 三个virtual Invoke / BeginInvoke / EndInvoke 方法.

    //同步方法

    Invoke : 参数的个数,类型, 返回值都不相同

     

     

    //异步方法,作为一组来说明

    BeginInvoke : 参数的个数和类型不同,返回值相同

    EndInvoke : 参数相同,返回值不同

     

    这里我们先介绍下 Invoke这个方法, 我们用SalaryEventHandler委托为例(直接调用Code 1 的类)

    Code 3

    复制代码
     1class Program
     2{
     3    static void Main(string[] args)
     4    {
     5        //添加辅助方法,查看当前的线程ID
     6        Console.WriteLine("Main Thread ID:#{0}", Thread.CurrentThread.ManagedThreadId);
     7
     8        AsyncTest test = new AsyncTest();
     9        //[1],我们习惯的调用方式
    10        decimal v1 = test.YearlySalary(10000015100000);
    11        //使用委托调用
    12        SalaryEventHandler salaryDelegate = test.YearlySalary;
    13        //[2],编译器会自动的把[2]转变成[3]Invoke的调用方式,[2]和[3]是完全相同的
    14        decimal v2 = salaryDelegate(10000015100000);
    15        //[3]
    16        decimal v3 = salaryDelegate.Invoke(10000015100000);
    17
    18        Console.WriteLine("V1:{0},V2:{1},V3:{2}", v1, v2, v3);
    19        Console.ReadLine(); // 让黑屏等待,不会直接关闭..
    20    }

    21}
    复制代码

    输出的结果

    3

    从结果可以看出,他们是同一个线程调用的(都是#10).这就说明[1],[2],[3]是同步调用

    [2],[3]对比[1], 只不过[2],[3]是通过委托的方式(其实我们可以说成“通过代理的方式完成”),[1]是直接的调用.举一个我们平常生活中例子:买机票,我们到代理点购买机票而不是直接跑到机场购买,就好像我们叫别人帮我们买机票一样,最后到手的机票是一样的, SalaryEventHandler就是我们的代理点.所以用代理的方式还是直接调用的方式,他们提供的参数和返回值必须是一样的.

     

    接下来我们开始讲异步机制核心的两个方法BeginInvoke/EndInvoke,他们作为一个整体来完成Invoke方法的调用,不同于Inoke方法的是他们是异步执行(另外开一个线程执行),下面先解释下他们的作用

    BeginInvoke : 开始一个异步的请求,调用线程池中一个线程来执行
    EndInvoke : 完成异步的调用, 处理返回值  异常错误.

    注意: BeginInvokeEndInvoke必须成对调用.即使不需要返回值,但EndInvoke还是必须调用,否则可能会造成内存泄漏.


    我们来对比下 SalaryEventHandler AsyncEventHandler委托反编译BeginInoke后的异同.

    SalaryEventHandler 委托:

    public virtual IAsyncResult BeginInvoke(decimal salary, int monthCount, decimal bonus, AsyncCallback callback, object @object)

    AsyncEventHandler 委托:

    public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object)

     

    可以看出参数的个数和类型是不同的,我们把焦点放到他们的相同点上,

    1,返回值是相同的: 返回IAsyncResult 对象(异步的核心). IAsyncResult是什么呢? 简单的说,存储异步操作的状态信息的一个接口,也可以用他来结束当前异步.具体的可以看下http://msdn.microsoft.com/zh-cn/library/system.iasyncresult(VS.80).aspx

     

    2,编译器会根据委托的参数个数和类型生成相应的BeginInvoke方法,只有最后两个参数是永远相同的,他提供一个AsyncCallback 委托(public delegate voidAsyncCallback(IAsyncResult ar);) 和一个 Object 对象.

     

    我们再来看看EndInvoke的异同.

    SalaryEventHandler 委托:

    public virtual decimal EndInvoke(IAsyncResult result)

    AsyncEventHandler 委托:

    public virtual string EndInvoke(IAsyncResult result)

     

    EndInvoke的参数是一样的, 唯一是在是返回值不同(他们会根据自己委托的返回值类型生成自己的类型)

     

    ,下面我会通过例子来说明BeginInvoke/EndInvoke,还是使用SalaryEventHandler委托为例(直接调用Code 1 的类)

     

    .Net Framework 提供了两种方式来使用异步方法

    第一种: 通过IAsyncResult 对象

    Code 4.1

    复制代码
     1class Program
     2{
     3    static IAsyncResult asyncResult;
     4
     5    static void Main(string[] args)
     6    {
     7
     8        AsyncTest test = new AsyncTest();
     9        SalaryEventHandler dele = test.YearlySalary;
    10        //异步方法开始执行,返回IAsyncResult(存储异常操作的状态信息) 接口,同时EndInvoke 方法也需要他来作为参数来结束异步调用
    11        asyncResult = dele.BeginInvoke(10000015100000nullnull);
    12        //获取返回值
    13        decimal val = GetResult();
    14        Console.WriteLine(val);
    15        Console.ReadLine(); // 让黑屏等待,不会直接关闭..
    16    }

    17
    18    static decimal GetResult()
    19    {
    20        decimal val = 0;
    21        //获取原始的委托对象:先是获取AsyncResult对象,再根据他的AsyncDelegate属性来调用
    当前的(那一个)委托对象
    22        AsyncResult result = (AsyncResult)asyncResult;
    23        SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
    24
    25        //调用EndInvoke获取返回值
    26        val = salDel.EndInvoke(asyncResult);
    27
    28        return val;
    29    }

    30}
    复制代码

    第二种: 通过回调函数. 使用倒数第二个参数AsyncCallback 委托(public delegate void AsyncCallback(IAsyncResult ar);) ,建议使用这种方法.

    Code 4.2

    复制代码
     1class Program
     2{
     3    static void Main(string[] args)
     4    {
     5        AsyncTest test = new AsyncTest();
     6        SalaryEventHandler dele = test.YearlySalary;
     7
     8        //异步方法开始执行,使用BeginInvoke 倒数第二个参数(AsyncCallback委托对象) ,而不用返回值
     9        dele.BeginInvoke(10000015100000, GetResultCallBack, null);
    10        //和上面相同的
    11        //AsyncCallback callback = new AsyncCallback(GetResultCallBack);
    12        //dele.BeginInvoke(100000, 15, 100000, callback, null);
    13
    14        Console.ReadLine(); // 让黑屏等待,不会直接关闭..
    15    }

    16
    17    //必须遵循AsyncCallback 委托的定义:返回值为空,一个IAsyncResult对象参数
    18    static void GetResultCallBack(IAsyncResult asyncResult)
    19    {
    20        decimal val = 0;
    21        //获取原始的委托对象
    22        AsyncResult result = (AsyncResult)asyncResult;
    23        SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
    24
    25        //调用EndInvoke获取返回值
    26        val = salDel.EndInvoke(asyncResult);
    27
    28        Console.WriteLine(val);
    29    }

    30}
    复制代码

     

    BeginInvoke最后一个参数是做什么的呢?我把Code 4.2 方法修改下.

    Code 4.3

    复制代码
     1class Program
     2{
     3    static void Main(string[] args)
     4    {
     5        AsyncTest test = new AsyncTest();
     6        SalaryEventHandler dele = test.YearlySalary;
     7
     8        //异步方法开始执行,看最后一个参数(Object对象) [Note1:],这里我们传递2000(int)
     9        dele.BeginInvoke(10000015100000, GetResultCallBack, 2000);
    10
    11        Console.ReadLine(); // 让黑屏等待,不会直接关闭..
    12    }

    13
    14    static void GetResultCallBack(IAsyncResult asyncResult)
    15    {
    16        //[Note1:],他的作用就是来 "传递额外的参数",因为他本身是Object对象,我们可以传递任何对象
    17        int para = (int)asyncResult.AsyncState;
    18        Console.WriteLine(para);//输出:2000
    19    }

    20}
    复制代码

     

    异步的异常处理

    接下来再讲讲EndInvoke,获取最后的返回值之外,他的一个重要的应用在引发异常来从异步操作返回异常

    Code 5

    复制代码
     1class Program
     2{
     3    static void Main(string[] args)
     4    {
     5        AsyncTest test = new AsyncTest();
     6        SalaryEventHandler dele = test.YearlySalary;
     7
     8        dele.BeginInvoke(10000015100000, GetResultCallBack, null);
     9        Console.ReadLine(); // 让黑屏等待,不会直接关闭..
    10    }

    11
    12    static void GetResultCallBack(IAsyncResult asyncResult)
    13    {
    14        decimal val = 0;
    15        //获取原始的委托对象
    16        AsyncResult result = (AsyncResult)asyncResult;
    17        SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
    18        try
    19        {
    20            //如果EndInvoke发生异常,会在EndInvoke得到原始的异常.
    21            val = salDel.EndInvoke(asyncResult);
    22            Console.WriteLine(val);
    23        }

    24        catch (Exception ex)
    25        {
    26            Console.WriteLine(ex.Message);
    27        }

    28    }

    29}

    30public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus); // 对应YearlySalary方法
    31public class AsyncTest
    32{
    33    public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
    34    {
    35        throw new Exception("error"); //引发异常
    36        return salary * monthCount + bonus;
    37    }

    38}
    复制代码

     

    我们主动在YearlySalary方法中引发异常,BeginInvoke开始异步调用的时候捕获到了这个异常,.Net Framework会在EndInvoke得到原始的异常.

     

    说到这里,大家是否可以简单的应用委托来开始自己的异步操作呢? 下面看看我是怎样为我自己的类添加异步的.

    1, 类的定义,需要遵循.Net Framework 的规则

    1)同步和异步是同时并存的

    2)从最上面的两个委托SalaryEventHandler AsyncEventHandler生成的BeginInvoke / EndInvoke 对比中看出,我们也来定义我们自己的异步方法,我们遵循微软设计师异步方法设计的规则,Begin+同步方法名 / End+同步方法名

    BeginXXX 必须返回IAsyncResult对象,后两位参数必须为AsyncCallback callback, object state,前面的参数和同步方法的参数一样

    EndXXX 参数必须为IAsyncResult对象,返回值为同步方法的返回值

     

    Code 6.1

    复制代码
     1public class AsyncTest
     2{
     3    private delegate string AsyncEventHandler(string name);
     4    private AsyncEventHandler _Async;
     5    public string Hello(string name)
     6    {
     7        return "Hello:" + name;
     8    }

     9
    10    //按照.Net Framework的规则 ,编写我们自己的BeginInvoke方法
    11    public virtual IAsyncResult BeginHello(string name, AsyncCallback callback, object state)
    12    {
    13        AsyncEventHandler del = Hello;
    14
    15        this._Async = del;
    16
    17        return del.BeginInvoke(name, callback, state);
    18    }

    19    //编写我们自己的EndInvoke方法
    20    public virtual string EndHello(IAsyncResult asyncResult)
    21    {
    22        if (asyncResult == null)
    23            throw new ArgumentNullException("asyncResult");
    24        if (this._Async == null)
    25            throw new ArgumentException("_Async");
    26
    27        string val = string.Empty;
    28        try
    29        {
    30            val = this._Async.EndInvoke(asyncResult);
    31        }

    32        finally
    33        {
    34            this._Async = null;
    35        }

    36        return val;
    37    }

    38}
    复制代码

     

    2: 调用我们编写的类

    Code 6.2

    复制代码
     1class Program
     2{
     3    static void Main(string[] args)
     4    {
     5        AsyncTest test = new AsyncTest();
     6        //使用回调函数,就是上面提到的"第二种"
     7        AsyncCallback callback = new AsyncCallback(OnHelloCallback);
     8        test.BeginHello("Andy Huang", callback, test);
     9        //和上面一样
    10        //IAsyncResult result = test.BeginHello("Andy Huang", OnHelloCallback, test);
    11
    12        Console.ReadLine(); // 让黑屏等待,不会直接关闭..
    13    }

    14
    15    static void OnHelloCallback(IAsyncResult asyncResult)
    16    {
    17        //获取额外的参数
    18        AsyncTest obj = (AsyncTest)asyncResult.AsyncState;
    19        string val = obj.EndHello(asyncResult);
    20        Console.WriteLine(val);
    21    }

    22}
    复制代码

     

    :Hello的方法的异步重构在下面的代码中可以下载到.

     

    下一篇中我们会说说异步中的一些高级应用,异步的核心,还有微软.Net Framework 为我们提供的各种类中(具有异步方法的类),我们是怎么使用这些方法的.最后要提醒下大家:滥用异步,会影响性能,而且增加编程难度

     

    以上有word 文档直接粘贴,排版可能不太看,你可以通过下面来下载相应的代码/文档

    1,文档

    2,代码 (VS2008开发,.Net Framework 2.0(C Sharp)编写)

  • 相关阅读:
    NGUI版虚拟摇杆
    设计模式 --外观模式(Facade)
    JavaScript提高:005:ASP.NET使用easyUI TABS标签显示问题
    提高工作效率:15个实用的项目管理工具
    前端开发面试题集锦(一)
    SWF代码分析与破解之路 (YueTai VIP视频信息获取工具) Socket续篇
    Android Studio 设置项目Module编码,解决Android Studio项目执行时乱码问题
    HDU2256-Problem of Precision(矩阵构造+高速幂)
    oracle初始安装大小
    强制杀oracle进程
  • 原文地址:https://www.cnblogs.com/ywsoftware/p/3105398.html
Copyright © 2020-2023  润新知