(十)异步-委托异步调用(4)

一、委托异步执行

当委托对象被调用时,它调用其调用列表中包含的方法。这是同步完成的。

如果委托对象在调用列表中只有一个方法(引用方法),它就可以异步执行这个方法。委托类有两个方法,叫作 BeginInvoke 和 EndInvoke。

  • 当调用委托的 BeginInvke 方法时,它开始在一个独立线程上执行引用方法,之后立即返回到原始线程。原始线程可以继续,而引用方法会在线程池的线程中并行执行。

  • 当程序希望获取已完成的异步方法的结果时,可以检查 BeginInvoke 返回的 IAsyncResult 的 IsCompleted 属性,或调用委托的 EndInvoke 方法来等待委托完成。

原始线程如何知道发起的线程已经完成:

  • 在等待直到完成(wait-until-done)模式中,在发起了异步方法以及做了一些其他处理之后,原始线程就中断并且等异步方法完成之后再继续。

  • 在轮询(polling)模式中,原始线程定期检查发起的线程是否完成,如果没有则可以继续做一些其他的事情。

  • 在回调(callback)模式中,原始线程一直执行,无须等待或检查发起的线程是否完成。

在发起的线程中的引用方法完成之后,发起的线程就会调用回调方法,由回调方法在调用 EndInvoke 之前处理异步方法的结果。

原始线程是操作系统内核在创建进程时创建的第一个线程。

请添加图片描述

二、BeginInvoke 和 EndInvoke

BeginInvoke:

  • 在调用 BeginInvoke 时,参数列表中的实际参数:

    • 引用方法需要的参数;
    • 两个额外的参数—— callback 参数和 state 参数。
  • BeginInvoke 从线程池中获取一个线程并且让引用方法在新的线程中开始运行。

  • BeginInvoke 返回给调用线程一个实现 IAsyncResult 接口的对象的引用。这个接口引用包含了在线程池线程中运行的异步方法的当前状态。然后原始线程可以继续执行。

delegate long MyDel(int first,int second);//委托声明
...
static long Sun(int x,int y){ return x + y; }//方法匹配委托
...
MyDel del = new MyDel(Sum);//创建委托对象

IAsyncResult iar = del.BeignInvoke(3,5,null,null);

//IAsyncResult iar:有关新线程的信息
//del.BeignInvoke:异步调用委托
//3,5:委托参数
//callback(null) 和 state(null):额外参数

//最后一行代码的程序执行步骤:
//1、从线程池中获取一个线程并且在新的线程上运行 Sum 方法,将3和5作为实参。
//2、它收集新线程的状态信息并且把 IAsyncResult 接口的引用返回给调用线程来提供这些信息。调用线程把它保存在一个叫作 iar 的变量中。

EndInoke 方法用来获取由异步方法调用返回的值,并且释放线程使用的资源。EndInvoke 有如下的特性。

  • 它接受一个由 BeginInvoke 方法返回的 IAsyncResult 对象的引用作为参数,并找到它关联的线程。

  • 如果线程池的线程已经退出,则 EndInvoke 做如下的事情。

    • 清理退出线程的状态并释放其资源。
    • 找到引用方法返回的值并把它作为返回值返回。
  • 如果当 EndInvoke 被调用时线程池的线程仍然在运行,调用线程就会停止并等待它完成,然后再清理并返回值。因为 EndInvoke 是为开启的线程进行清理,所以必须确保对每一个 BeginInvoke 都调用 EndInvoke。

  • 如果异步方法触发了异常,则在调用 EndInvoke 时会抛出异常。

long result = del.EndInvoke(iar);
//long result:异步方法的返回值
//del:委托对象
//iar:IAsyncResult 对象

EndInvoke 提供了异步方法调用的所有输出,包括 ref 和 out 参数。如果委托的引用方法有 ref 或 out 参数,则它们必须包含在 EndInvoke 的参数列表中,并且在 IAsyncResult 对象引用之前:

long result = del.EndInvoke(out someInt,iar);
//long result:异步方法的返回值
//out someInt:Out 参数
//iar:IAsyncResult 对象

等待直到完成模式

IAsyncResult iar = del.BeginInvoke(3,5,null,null);
//在发起线程中异步执行方法的同时,
//在调用线程中处理一些其他事情
...
long result =del.EndInvoke(iar);

示例:

   delegate long MyDel(int first, int second);//声明委托类型

    class Program
    {
        static long Sum(int x,int y)
        {
            Console.WriteLine("      Inside Sum");
            return x + y;
        }

        static void Main(string[] args)
        {
            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:{ result }");
            Console.ReadKey();
        }
    }

输出结果:

Before BeginInvoke
After BeginInvoke
Doing stuff
Inside Sum
After EndInvoke:8

三、AsyncResult 类

BeginInvoke 返回一个 IASyncResult 接口的引用(该接口由一个 AsynResult 类型的类实现)。AsyncResult 类代表了异步方法的状态。

  • 当我们调用委托对象的 BeginInvke 方法时,系统创建了一个 AsyncResult 类对象。然而,它不返回类对象的引用,而是返回对象中包含的 IAsyncResult 接口的引用。

  • AsyncResult 对象包含一个叫作 AsyncDelegate 的属性,它返回一个指向被调用来启动异步方法的委托的引用。但是,这个属性是类对象的一部分而不是接口的一部分。

  • IsCompleted 属性返回一个布尔值,表示异步方法是否完成。

  • AsyncState 属性返回对象的一个引用,作为 BeginInvoke 方法调用时的 state 参数。它返回 object 类型引用。

请添加图片描述

四、轮询模式

在轮询模式中,原始线程发起了异步方法的调用,做一些其他处理,然后使用 IAsyncResult 对象的 IsComplete 属性来定期检查开启的线程是否完成。如果异步方法已经完成,原始线程就调用 EndInvoke 并继续。否则,它做一些其他处理,然后过一会儿再检查。

示例:

   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(string[] args)
        {
            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:{ result }");

            Console.ReadKey();
        }
    }

输出结果:

After BeginInvoke
Not Done
Inside Sum
Not Done
Done
Result:8

五、回调模式

一旦初始线程发起了异步方法,它就自己管自己了,不再考虑同步。当异步方法带哦用结束之后,系统调用一个用户自定义的方法(回调方法)来处理结果,并且调用委托的 EndInvoke 方法。

BeginInvoke 参数:

  • callback: 回调方法的名字(是 AsyncCallback 类型的委托)
  • state: 要传入回调方法的一个对象的引用。(任何类型的对象)

1、回调方法

void AsyncCallback(IAsyncResult iar)

使用回调方法

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

2、在回调方法内调用 EndInvoke

如果不将 BeginInvoke 的 state 参数用于其他目的,可以使用它给回调方法发送委托的引用:

IAsyncResult IAR = del.BeginInvoke(3,5,CallWhenDone,del);
//del:委托对象
//第二个del:把委托对象作为状态参数发送

可以从作为参数发送给方法的 IAsyncResult 对象中提取出委托的引用。

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

long Sum = del.EndInvoke(iar);//调用EndInvoke
...
}

请添加图片描述

使用回调模式的示例:

    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 result = del.EndInvoke(iar);
            Console.WriteLine("               The result is : {0}.", result);
        }

        static void Main(string[] args)
        {
            MyDel del = new MyDel(Sum);

            Console.WriteLine("Before BeginInvoke");
            IAsyncResult iar = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone), null);

            Console.WriteLine("Doning more work in Main.");
            Thread.Sleep(500);
            Console.WriteLine("Done with Main. Exiting.");
            Console.ReadKey();
        }
    }

输出结果:

Before BeginInvoke
Inside Sum
Doning more work in Main.
Inside CallWhenDone.
The result is : 8.
Done with Main. Exiting.

六、计时器

一种定期重复运行异步方法的方法。

  • 计时器在每次到期之后调用回调方法。
void TimerCallback(object state)
  • 当计时器到期之后,系统会在线程池中的一个线程上设置回调方法。
  • 可以设置的计时器的特性:
    • dueTime: 回调方法首次被调用之前的时间。如果 dueTime 被设置为特殊的值 Timeout.Infinite,则计时器不会开始;如果被设置为0,则回调函数被立即调用。
    • period: 两次成功调用回调函数之间的时间间隔。如果它的值被设置为 Timeout.Infinite,则回调在首次被调用之后不会再被调用。
    • state: 可以是 null 或在每次回调方法执行时要传入的对象的引用。
Timer(TimerCallback callback, object state, uint dueTime, uint period)

如果创建了 Timer 对象的实例:

Timer myTimer = new Timer(MyCallback, someObject, 2000, 1000);
//MyCallback:回调的名字
//someObject:传给回调的对象
//2000:在2000毫秒后第一次调用
//1000:每1000 毫秒调用一次

示例:

  class Program
    {
        int TimmesCalled = 0;
        void Display(object state)
        {
            Console.WriteLine($"{ (string)state } {  ++TimmesCalled }");
        }
        static void Main(string[] args)
        {
            Program p = new Program();

            Timer myTimer = new Timer(p.Display, "Processing timer event", 2000, 1000);
            Console.WriteLine("Timer started.");
            Console.ReadLine();
            Console.ReadKey();
        }
    }

书上说大概5秒后会终止输出结果,但是我的编程环境里,并不会这样,会一直不停地输出。代码例子和书上的一模一样的。按理说,这并不会结束地呼出结果,才是对的吧?先不管了,以后遇到了这个情况再看看。

猜你喜欢

转载自blog.csdn.net/chen1083376511/article/details/131268168
今日推荐