• 什么是.Net的异步机制(Invoke,BeginInvoke,EndInvoke) 《转载》


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

      现在扩充下上篇文章的类(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(100000, 15, 100000);
    11    //使用委托调用
    12    SalaryEventHandler salaryDelegate = test.YearlySalary;
    13    //[2],编译器会自动的把[2]转变成[3]Invoke的调用方式,[2]和[3]是完全相同的
    14    decimal v2 = salaryDelegate(100000, 15, 100000);
    15    //[3]
    16    decimal v3 = salaryDelegate.Invoke(100000, 15, 100000);
    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 : 完成异步的调用, 处理返回值 和 异常错误.

      注意: BeginInvoke和EndInvoke必须成对调用.即使不需要返回值,但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 void AsyncCallback(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(100000, 15, 100000, null, null);
    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(100000, 15, 100000, 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(100000, 15, 100000, 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(100000, 15, 100000, 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)同步和异步是同时并存的

     
  • 相关阅读:
    Xcode中设置按钮在十分钟之内禁用
    xcode4.3 完成输入后 点击背景关闭键盘
    [转] Xcode4.4.1下安装高德地图详细教程
    [转]在iOS项目中使用CorePlot框架
    【转】利用xcode生成的app生成可以在iphone和itouch上运行的ipa安装包
    【转】cvs2svn 把CVS档案库转换为SVN档案库
    【转】MYSQL入门学习之十三:自定义函数的基本操作
    【转】MYSQL入门学习之十二:存储过程的基本操作
    【转】MYSQL入门学习之十一:触发器的基本操作
    【转】MYSQL入门学习之十:视图的基本操作
  • 原文地址:https://www.cnblogs.com/MrLi/p/3185797.html
Copyright © 2020-2023  润新知