异步编程(十一):委托方法的BeginInvoke和EndInvoke

委托对象的调用列表中只有一个方法(我们通常称之为引用方法)时,可以进行异步执行。委托类有两个方法:BeginInvoke和EndInvoke就是用来实现这一效果的。

  • 当我们调用委托的BeginInvoke方法时,将会在一个独立的线程执行委托的引用方法,并立即返回到原始线程(即调用方法的位置),继续向下执行。此时委托的引用方法在线程池中并行执行。
  • 当程序希望获取已完成的异步方法结果时,可以检查BeginInvoke返回的IAsycnResult的IsCompleted属性,或者调用委托的EndInvoke方法来等待委托的完成。

通常我们在实际使用过程中,这种异步执行的方法常常采用三种模式:

  1. 等待完成模式(wait-until-done):发起异步方法后,就去完成其他处理内容,然后原始线程就中断并等待异步方法完成后才继续其他执行。
  2. 轮询模式(polling):原始线程通过循环定期检查异步方法是否完成,如果没有则继续做其他事情。
  3. 回调模式(callback):原始线程发起异步方法后就不在理会和检查结果。异步方法在完成后,自行调用回调方法,由回调方法在调用EndInvoke之前处理异步方法的结果。

1. BeginInvoke和EndInvoke方法

首先我们需要了解BeginInvoke的一些重要事项:

  • BeginInvoke 参数的组成:
    • 委托的引用方法自身的参数;
    • 两个额外参数:callback 和state参数。
  • BeginInvoke从线程池中获得一个线程并让引用方法在此线程中开始运行。
  • BeginInvoke会返回一个实现IAsyncResult接口的对象,这个对象可以反映线程中异步方法的实时状态。
    例如:
delegate long MyDel(int first, int second);
...
static long Sum (int x,int y){...}
...
MyDel del=new MyDel(Sum);
IAsyncResult iar = del.BeginInvoke(3,5,null,null);

EndInvoke 用来获取异步方法调用返回的值,并且清除释放线程中使用的资源。特征如下:

  • 它接受BeginInvoke返回的IAsyncResult对象,并由此找到关联的线程;
  • 如果线程池中的线程已经退出,则做如下两件事:
    • 清理线程状态并释放其资源;
    • 如果引用方法有返回值,则将其返回。
  • 如果线程仍在运行,EndInvoke将停止并等待,直到方法完成,然后清理线程并获取返回值。因为EndInvoke负责线程清理工作,因此每一个BeginInvoke都必须有对应的EndInvoke相匹配。
  • 如果异步方法触发了异常,将在调用EndInvoke方法时抛出。

如下代码示例了一个EndInvoke的调用,它必须以IAsyncResult对象的引用为参数:

long result = del.EndInvoke(iar);

EndInvoke提供了从异步方法返回的所有输出,包括ref和out参数。如果委托的引用方法有ref和out参数,它们就必须包含在EndInvoke的参数列表中。例如:

long result = del.EndInvoke(out int someInt, iar);

2. 等待完成模式

在这种模式下,原始线程发起一个异步方法的调用,做一些事情处理,然后停止并等待,直到开启线程结束。

using System;
using System.Threading;

delegate long MyDel (int first, int second);
class Program
{
    static long Sum(int x, int y)
    {
        Console.WriteLine(“        Inside Sum”);
        Thread.Sleep(100);
        return x+y;
    }
    
    static void Main()
    {
        MyDel del=new MyDel(Sum);
        Console.WriteLine(“Before BeginInvoke”);
        IAsyncResult iar=del.BeginInvoke(3, 5, null, null);
        Console.WriteLine(“ After BeginInvoke”);
        Console.WriteLine(“Doing stuff”);
        
        long result = del.EndInvoke(iar);
        Console.WriteLine(“After EndInvoke:{0}”, result);
    }
}

3.AsyncResult类

AsyncResult 是方法的必要部分,BeginInvoke 返回一个IAsyncResult接口的引用(内部是AsyncResult类的对象)。AsyncResult类表现了异步方法的状态。

  • 当我们调用委托对象的BeginInvoke方法时,系统创建一个AsyncResult类的对象,然后它不返回类对象的引用,而是返回对象中包含的IAsyncResult接口的引用。
  • AsyncResult对象包含一个叫做AsyncDelegate的属性,它返回一个指向被调用来开启异步方法的委托的引用。注意,这个属性是类对象的一部分而不是接口的一部分。
  • IsCompleted 属性返回异步方法是否完成的布尔值。
  • AsyncState返回一个对象引用,做为BeginInvoke 方法调用时的state参数。它返回object类型的引用,只有在回调模式中使用。

4. 轮询模式

在轮询模式中,原始线程发起了异步方法的调用,做一些其他处理,然后使用IAsyncResult对象的IsCompleted属性定期检查开启的线程是否完成。如果完成则继续向下执行,否则继续做它的其他处理,直到下一次检查。

    delegate long MyDel(int x, int y);
    class program
    {
        long Sum(int x, int y)
        {
            Console.WriteLine("        Inside Sum");
            Thread.Sleep(100);
            return x + y;
        }

        static void Main()
        {
            MyDel del = new MyDel(Sum);

            IAsyncResult iar = del.BeginInvoke(3, 5, null, null);
            Console.WriteLine("After BeginInvoke");
            while (!iar.IsCompleted)
            {
                Console.WriteLine("Not Done");
                for (long i = 0; i < 10000000; i++) ;
            }
            Console.WriteLine("Done");

            long result = del.EndInvoke(iar);
            Console.WriteLine("Result:{0}", result);
        }
    }

4. 回调模式

在之前的等待结束模式和轮询模式中,初始线程都是继续自己的控制流程,直到它知道开启的线程已经完成,然后获取结果并继续。
回调模式则与之不同,一旦初始线程发起了异步方法,由它自己管理,就不再进行管理。异步方法调用结束后,系统自动调用一个用户自定义的方法来自行处理结果,并且调用委托的EndInvoke方法。这个用户自定义的方法做做回调方法或简称回调
BeginInvoke的参数列表中最后两个额外参数就是供回调方法使用的。

  • 第一个参数callback,是回调方法的名字;
  • 第二个参数state,可以是null或要传入回调方法的一个对象引用。我们可以通过使用IAsyncResult参数的AsyncState属性来获取这个对象,参数类型是object。

1)回调方法

回调方法的签名和返回类型必须与AsyncCallback委托类型所描述的形式一致。它需要方法接受一个IAsyncResult作为参数并且返回类型是void,如下所示

 void AsyncCallback(IAsysnResult iar)

我们有多种方式可以为BeginInvoke方法提供回调方法。由于BeginInvoke中的callback参数是AsyncCallback类型的委托,我们可以以委托形式提供,或者,我们也可以只提供回调方法的名称,让编译器为我们创建委托,两种方法是完全等价的。

IAsyncResult iar1=del.BeginInvoke(3,5,new AsyncCallback(CallWhenDone),null)
IAsyncResult iar2=del.BeginInvoke(3,5,CallWhenDone,null)

BeginInvoke的另外一个参数是发送给回调方法的对象。它可以是任何类型的对象,但是参数类型是object。所以在回调方法中,我们必须转换成正确的类型。

2)在回调方法中调用EndInvoke

在回调方法内,我们的代码应该调用委托的EndInvoke方法来处理异步方法执行后的输出值。要调用委托的EndInvoke方法,我们肯定需要委托对象的引用,而它在初始线程中,不在开启的线程中。如果不使用state参数作为其他用途,可以使用它发送委托的引用给回调方法。

IAsyncResult iar = del,BeginInvoke(3,5,CallWhenDone,del);
  • 给回调方法的参数只有一个,就是刚结束的异步方法的IAsyncResult接口的引用。请记住:IAsyncResult接口对象在内部就是AsyncResult类对象。
  • 尽管IAsyncResult接口没有委托对象的引用,而封装它的AsyncResult类对象却有委托对象的引用。所以可以通过转换接口引用为类类型来获取类对象的引用。
  • 有了类对象的引用,我们现在就可以调用类对象的AsyncDelegate属性并且把它转换为合适的委托类型。这样就得到了委托引用,我们可以用它来调用EndInvoke方法。
using System.Runtime.Remoting.Messaging;        //包含AsyncResult类
        void CallWhenDone(IAsyncResult iar)
        {
            AsyncResult ar = (AsyncResult)iar;      // 获取类对象的引用
            MyDel del = (MyDel)ar.AsyncDelegate;    // 获取委托的引用
            long sum = del.EndInvoke(iar);          // 调用EndInvoke方法
            ...
        }       

如此我们就完成了一个使用回调模式的完整示例。为了方便阅读,我们把所有知识点放在一起:

using System;
using System.Threading;
using System.Runtime.Remoting.Messaging; //包含AsyncResult类
namespace YbydjyLibrary
{
    delegate long MyDel(int first, int second);
    class Program
    {
        static long Sum(int x, int y)
        {
            Console.WriteLine("            Inside Sum");
            Thread.Sleep(100);
            return x + y;
        }

        static void CallWhenDone(IAsyncResult iar)
        {
            Console.WriteLine("            Inside CallWhenDone");
            AsyncResult ar = (AsyncResult)iar;      // 获取类对象的引用
            MyDel del = (MyDel)ar.AsyncDelegate;    // 获取委托的引用

            long sum = del.EndInvoke(iar);          // 调用EndInvoke方法
            Console.WriteLine($"Result:{sum}");
        }

        static void Main()
        {
            MyDel del = new MyDel(Sum);
            Console.WriteLine("Before BeginInvoke");
            IAsyncResult iar = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone), null);
            Console.WriteLine("Doing more work in Main");
            Thread.Sleep(500);
            Console.WriteLine("Done with Main. Exiting.");
        }       
    }
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值