1 2

c# 基于委托的异步编程模型(APM)测试用例

很多时候,我们需要程序在执行某个操作完成时,我们能够知道,以便进行下一步操作。

但是在使用原生线程或者线程池进行异步编程,没有一个内建的机制让你知道操作什么时候完成,为了克服这些限制,基于委托的异步编程模型应运而生。

通过定义回调函数能够实现异步编程,委托是一个工具,类似语c++的函数指针,当我们在使用委托时。可以传入一个符合其定义的方法。从编译器的层面看,定义一个委托,相当于定义了一个类,该类拥有一个委托链,还有三个方法,Invoke用于同步调用,而BeginInvoke和EndInvoke则是用于异步调用。

先来对这三个方法进行说明:

Invoke方法的输入输出和委托本身相同。注意:Invoke方法的主要功能就是帮助你在UI线程上调用委托所指定的方法。Invoke方法首先检查发出调用的线程(即当前线程)是不是UI线程,如果是,直接执行委托指向的方法,如果不是,它将切换到UI线程,然后执行委托指向的方法。不管当前线程是不是UI线程,Invoke都阻塞直到委托指向的方法执行完毕,然后切换回发出调用的线程(如果需要的话),返回。

BeginInvoke的输出是IAsyncResult,输入除了委托本身的输入,还包括一个回调函数AsyncCallBack,以及包括了一个object的类型参数,允许我们向异步委托传递任何类型的信息。如果把一个回调函数传入BeginInvoke,它会在委托运行完成后自动执行。

EndInvoke方法的输入总是IAsyncResult,输出则是和委托本身的输出相同。如果调用EndInvoke时,IAsyncResult对象表示的异步操作还未完成,则EndInvoke将在异步操作未完成之前阻塞调用线程(非常重要)。

如何理解这些方法的输入输出?我们可以把BeginInvoke 作为调用的开始,当调用完时,我们希望有一个回调函数,可以在希望的时候调用,从而让异步方法主动通知我们自己以及完成。so BeginInvoke的输入除了委托本身的输入外,还需要一个回到函数AsyncCallBack(当然也可以不写这个函数,传入null,这将失去主动通知的好处)。另外一个object类型的state参数,允许我们向异步委托传输任意类型的信息。这个参数,我们能在回调函数中获取到,这更加方便实现各种业务逻辑。

示例1:获取异步委托的执行结果

我们先将回调函数和状态设为null,通过EndInvoke异步委托获取目标函数的返回值,和Threading的方法相比,我们可以在委托目标的函数中设定返回值的类型,使获取结果容易了很多,但是这样会造成阻塞。因为EndInvoke强制获取结果,所以结果在没有计算出来之前,代码是无法向前进行的。这和Threading加轮询的方法类型,只是现在线程由线程池管理。

 class Program
    {
        public delegate bool IsPrimeSlowDelegate(int number);

        public static bool IsPrimeSlow(int number)
        {
            bool b = false;
            if (number <= 0)
            {
                throw new Exception("参数必须大于0");
            }
            if (number == 1)
            {
                throw new Exception("1既不是质数也不是合数");
            }
            for (int i = 2; i <= number; i++)
            {
                Thread.Sleep(100);
                if (number % i == 0)
                {
                    b = false;
                }
                else
                {
                    b = true;
                }

            }
            return b;

        }

        static void Main(string[] args)
        {
            IsPrimeSlowDelegate slowDelegate = new IsPrimeSlowDelegate(IsPrimeSlow);
            Console.WriteLine("开始执行:" + DateTime.Now.ToString());
            var ar = slowDelegate.BeginInvoke(120,null,null);

            var er = slowDelegate.EndInvoke(ar);
            Console.WriteLine("结束执行:" + DateTime.Now.ToString());
            Console.ReadKey();

        }
    }

执行结果:(在执行时,EndInvoke一直在等待计算结果,阻塞了主线程12秒,实际上和同步调用无异,因此,我们应该使用回调函数)

 

 

BeginInvoke和EndInvoke是由一个IAsyncResult接口对象联系在一起

System.IAsyncResult接口包括:

1、一个object类型的AsyncState,存储主线程传来的的信息。

2、一个布尔类型,IsCompleted,当其为真,异步委托执行完毕。

3、一个类型为WaitHandle的属性AsyncWaitHandle.WaitHandle有个方法WaitOne,可以指定最长等待时间。

示例:

static void Main(string[] args)
        {
            IsPrimeSlowDelegate slowDelegate = new IsPrimeSlowDelegate(IsPrimeSlow);
            Console.WriteLine("开始执行:" + DateTime.Now.ToString());
            IAsyncResult ar = slowDelegate.BeginInvoke(120,null,null);
            //设置线程最长等待时间为3秒
            bool b = ar.AsyncWaitHandle.WaitOne(3000);
            if (b)
            {
                //三秒后,如果当前实例收到信号,则为 true;否则为 false。当没有收到信息,就跳过执行EndInvoke
                var er = slowDelegate.EndInvoke(ar);
            }

            
            Console.WriteLine("结束执行:" + DateTime.Now.ToString());
            Console.ReadKey();

        }

 

示例2:通过回调函数的方式获得异步委托的执行结果

回调函数的作用是当委托完成后,可以主动通过主线程自己已经完成。我们可以在BeginInvoke中定义回调函数,这将在委托完成后自动执行,也就是说,线程将执行完回调函数才回到线程池,而不是直接回到池子中。

回到函数的类型是AsyncCallBack,其也是一个委托,传入参数必须是IAsyncResult,而且没有返回值,那么我们怎么获取返回值呢?

此时,我们可以通过回调函数的传入参数IAsyncResult来做。我们把参数显示转换为AsyncResult的形式。AsyncResult实现了IAsyncResult,它的属性AsyncDelegate是object类型的,可以指向其他地方对象的引用。此时我们可以在回调函数中建立一个委托,令其类型和main中建立的委托相同,然后,将其赋值给AsyncDelegatee(需要转换)。这时,该委托就和Main中的那个毫无二致。现在我们可以调用EndInvoke获取结果了。而且这次调用不会阻塞,代码如下

 static void Main(string[] args)
        {

            IsPrimeSlowDelegate slowDelegate = new IsPrimeSlowDelegate(IsPrimeSlow);
            var callBack = new AsyncCallback(CallBack);

            Console.WriteLine("开始执行:" + DateTime.Now.ToString());
            slowDelegate.BeginInvoke(123,cancelltion.Token,callBack,"我是最后一个参数");


            Console.WriteLine("结束执行:" + DateTime.Now.ToString());
            Console.Read();


        }

        public delegate bool IsPrimeSlowDelegate(int number);

        public static bool IsPrimeSlow(int number)
        {
            bool b = false;
            if (number <= 0 )
            {
                throw new Exception("参数必须大于0");
            }
            if (number == 1)
            {
                throw new Exception("1既不是质数也不是合数");
            }
            for (int i = 2; i <= number;i++)
            {
                Thread.Sleep(100);
                if (number%i == 0)
                {
                    b = false;
                }
                else
                {
                    b = true;
                }
            }
            return b;
        }

        /// <summary>
        /// 回调函数
        /// </summary>
        /// <returns></returns>
        public static void CallBack(IAsyncResult iar)
        {
             var ar = (AsyncResult)iar;
            var br = (IsPrimeSlowDelegate)ar.AsyncDelegate;
            try
            {
                var re = br.EndInvoke(iar);
                Console.WriteLine(re.ToString());
            }
            catch (Exception exp)
            {
                Console.WriteLine(exp.Message);
            }
            
            

        }

主线程立即执行完成,回调函数在计算完成后自动调用。

回调函数的参数对象:

 

这就是真正的异步了,主线程拍完任务后还可以做其他事,而子线程在任务完成后执行回调函数。

主线程还可以向子线程传输任何类型的自定义数据,这通过BeginInvoke的最后一个参数实现。由于它是object类型,所以任何类型都可以传输。在子线程中,我们通过调用IAsyncResult的AsyncState属性获取主线程传来的数据。该示例中,红圈即为传递的参数

 

示例3:使用线程统一取消模型进行取消

使用委托的异步编程模型也可以使用线程统一取消模型进行取消。首先,我们需要为为委托和委托目标方法加入CancellationToken输入参数(必须修改委托定义才行,因为BeginInvoke不支持传入CancellationToken)。

然后在委托目标方法中,调用ThrowIfCancellationRequested(在循环中,保持一直监听),最后,需要在回到函数中加入try-catch,因为BeginInvoke不抛OperationCanceledException异常,只有EndInvoke才会。为了不阻塞主线程,回调函数获取结果的EndInvoke是唯一 的选择。代码如下

 static void Main(string[] args)
        {
            //创建取消多线程的对象
            CancellationTokenSource cancelltion = new CancellationTokenSource();

            IsPrimeSlowDelegate slowDelegate = new IsPrimeSlowDelegate(IsPrimeSlow);
            var callBack = new AsyncCallback(CallBack);

            Console.WriteLine("开始执行:" + DateTime.Now.ToString());
            slowDelegate.BeginInvoke(123,cancelltion.Token,callBack,"我是最后一个参数");


            Console.WriteLine("结束执行:" + DateTime.Now.ToString());
            Console.ReadKey();
            Console.WriteLine("线程取消执行:"+DateTime.Now.ToString());
            cancelltion.Cancel();
            Console.Read();

        }

        public delegate bool IsPrimeSlowDelegate(int number,CancellationToken token);

        public static bool IsPrimeSlow(int number,CancellationToken token)
        {
            bool b = false;
            if (number <= 0 )
            {
                throw new Exception("参数必须大于0");
            }
            if (number == 1)
            {
                throw new Exception("1既不是质数也不是合数");
            }
            for (int i = 2; i <= number;i++)
            {
                Thread.Sleep(100);
                token.ThrowIfCancellationRequested();
                if (number%i == 0)
                {
                    b = false;
                }
                else
                {
                    b = true;
                }
            }
            return b;
        }

        /// <summary>
        /// 回调函数
        /// </summary>
        /// <returns></returns>
        public static void CallBack(IAsyncResult iar)
        {
            var ar = (AsyncResult)iar;
            var br = (IsPrimeSlowDelegate)ar.AsyncDelegate;
            try
            {
                var re = br.EndInvoke(iar);
                Console.WriteLine(re.ToString());
            }
            catch (OperationCanceledException cancelExp)
            {
                Console.WriteLine("任务取消");
            }
            catch (Exception exp)
            {
                Console.WriteLine(exp.Message);
            }
            
        }

 

 

 取消执行回调函数中,EndInvoke抛出异常:

注意:用户在任务完成之前取消了,会抛出异常,任务完成后取消,并不会抛出异常

即使提前取消了任务,也会调用回调函数,抛出异常。

无论任务成功完成还是被取消,IAsyncResult对象的Is'Com'p'leted属性都是true;

如果委托传入小于0,和1的数据,也会抛出异常,为了捕获此类以及其他非取消线程异常。(使用catch(Exception exp)进行捕获)

posted @ 2019-06-05 15:44  大海的泡沫  阅读(683)  评论(0编辑  收藏  举报
1 2