• 异步编程模式


    1. 程序的同步执行和异步执行   

    2. 等待异步调用的完成   

    3.异步调用中的异常   

    4.实现IAsyncResult异步调用模式的组件   

    1.程序的同步执行和异步执行

    在许多程序中代码是顺序执行的,如果在代码中调用了一个方法,则必须等待此方法所有的代码执行完毕之后,才能回到原来的地方执行下一行代码,这种程序运行方式称为同步

    示例程序

    class Program

        {

            static void Main(string[] args)

            {

                long size;

                string foldname;

                Console.WriteLine("请输入文件夹名称:");

                foldname = Console.ReadLine();

                size = CalculateFolderSize(foldname);

                Console.WriteLine("文件夹{0}容量为{1}", foldname, size);

                Console.ReadKey();

            }

            public static long CalculateFolderSize(string folderName)

            {

                if (Directory.Exists(folderName))

                {

                    new Exception("文件夹不存在");

                }

                DirectoryInfo direRoot = new DirectoryInfo(folderName);

                DirectoryInfo[] dire = direRoot.GetDirectories();

                FileInfo[] file= direRoot.GetFiles();

                long size=0;

                foreach (var f in file)

                {

                    size += f.Length;

                }

                foreach (var d in dire)

                {

                    size += CalculateFolderSize(d.FullName);

                }

                return size;

            }

        }

    执行结果:

    注意  size = CalculateFolderSize(foldname)这句,如果此方法不返回,则第8句不可能执行

    如果CalculateFolder方法执行需要一定时间(层次很深,文件很多),则用户在方法返回前看不到任务信息,有可能会以为死机了。

    能不能在调用方法之后,不用等待方法执行完成就马上执行下一条语句

    要实现这个功能,就必须采用异步编程模式

    修改上述代码Main中方法,采用委托实现

      public delegate long CalCulateFolderSizeDelegate(string foldername);       

      static void Main(string[] args)
            {
                CalCulateFolderSizeDelegate d = CalculateFolderSize;
                Console.WriteLine("请输入一个数:");
                string foldname = Console.ReadLine();
                IAsyncResult ret = d.BeginInvoke(foldname, null, null);
                Console.WriteLine("正在计算中,请耐心等待");
                long size = d.EndInvoke(ret);
                Console.WriteLine("计算完成,文件夹{0}容量为{1}", foldname, size);
                Console.ReadKey();
            }

    执行结果:

    注意上面红色处:执行CalculateFolderSize之后,并不会等待其完成,而是马上执行后面语句,输出一句提示信息,"正在计算中,请耐心等待"。

    上述代码中IAsyncResult ret = d.BeginInvoke(foldname, null, null);

    通过委托对像d的beginInvoke方法间接地调用静态方法CalculateFolderSize,这就是异步调用

    异步调用的关键之处在于静态方法CalculateFolderSize不在主线程(即Main所在方法)中执行,而在另一个辅助线程中与主线程代码并行执行,由于存在两个并行执行的线程,所以启动执行静态之后,必须有办法取回其计算结果,EndInvoke方法可完成这一任务,但它需要一些额外的信息,这些信息是BeginInvoke方法启动异步调用时提供的,这就是BeginInvoke方法的返回值ret,一个IAsyncResult类型的对像,此对像将成为EndInvoke方法的参数。

    EndInvoke方法在执行时,如果CalcuateFolderSize方法还未返回,它会停在这儿等待。

    2.等待异步调用的完成

    在上述异步调用示例中,用户只是看到了一条固定的信息: “正在计算中,请耐心等待…..”,就没有下文了,显然交互不不太好,虽然做不到拥有可视化窗体的window应用程序那样丰富,但做一点小的改进还是可以的,我们可以在程序执行异步调用的过程中,让计算机每隔一段时间(比如2秒)向控制台输出一个小点,告诉用户搜索工作正在进行中,从而可以大大改善程序用户的友好性,

     public delegate long CalCulateFolderSizeDelegate(string foldername);
            static void Main(string[] args)
            {
                long size = 0;
                CalCulateFolderSizeDelegate d = CalculateFolderSize;
                Console.WriteLine("输入一个文件名");
                string foldname = Console.ReadLine();
                IAsyncResult ret = d.BeginInvoke(foldname, null, null);
                Console.WriteLine("正在计算中,请耐心等待");
                while (ret.IsCompleted == false)
                {
                    Console.Write(".");
                    System.Threading.Thread.Sleep(2000);
                }
                size = d.EndInvoke(ret);
                Console.WriteLine("计算完成,文件夹{0}容量为{1}", foldname, size);
                Console.ReadKey();
            }

    IsCompleted属性值的方式不断询问异步调用是否完成,还可以使用IAsyncResult提供的一个属性AsyncWaitHandle

    现在每隔2秒,判断异步执行是否完成,未完成输出一个小点,用户体验是好了些,但是这无疑会在循环等待上浪费不少cpu时间,能不能让异步调用的方法在结束时自动调用一个方法,并在这个方法中显示处理结果?

    这种情况可以使用异上回调,BeginInvoke方法定义中的最后两个参数是AsyncCallback callback和object asyncState这两个参数就是用于异步调用的

    再次改进下程序,使之可以连续输入多个文件夹名称,计算机在后台分别计算,完成后就在控制台窗口中输出结果

    示例: 

           public delegate long CalculateFolderSizeDelegate(string foldname);

            private static CalculateFolderSizeDelegate d = CalculateFolderSize;

            static void Main(string[] args)

            {

                string foldname = "";

                while (true)

                {

                    Console.WriteLine("请输入文件夹名称,输入quit结束程序");

                    foldname = Console.ReadLine();

                    if (foldname == "quit")

                        break;

                    d.BeginInvoke(foldname, ShowFolderSize, foldname);

                }

            }

            public static void ShowFolderSize(IAsyncResult result)

            {

                long size = d.EndInvoke(result);

                Console.WriteLine("计算完成,文件夹{0}容量为{1}",result.AsyncState.ToString(), size);

             }

    执行结果:

    先执行的任务并不一定先完成,因为如果后执行的任务工作量小,反而先执行完毕

    注意这句 d.BeginInvoke(foldname, ShowFolderSize, foldname)

    BeginInvoke方法的第2个参数指定当异步调用结束时回调ShowFolderSize方法,第3个参数asyncState被填入了要计算的文件夹名字,此值被BeginInvoke方法包装到自动创建的一个IAsyncResult类型的对像中,并作为方法实参自动传送给回调方法,回调方法通过这一实参的AsyncState字段获取其值。

     

    3.异步调用中的异常

    同步模式下的异常处理方法,就是在调用此方法的代码处用try和catch处理异常,由于发生异常的代码与调用代码位于同一线程中,因些当异常发生时,计算机会中断当前线程的执行流程,转去执行异常处理代码。

    但在异步调用的过程中有异常是如何处理呢?

    当一个异步的方法抛出一个异常时,CRL会捕获它,当启动异步调用线程调用EndInvoke方法等待异步调用结束时,CLR会将此异常再次抛出,这样调用者线程即可捕获它。

    如果在调用BeginInvoke方法启动异步调用时提供了一个回调方法,则CLR会在捕获方法抛出的异常之后马上调用被回调的方法,而回调方法通常都需要调用EndInvoke方法

    总之在EndInvoke方法所在的代码处即可捕获异步调用的异常

    示例:

    public static void ShowFolderSize(IAsyncResult result)

            {

                try

                {

                    long size = d.EndInvoke(result);

                    Console.WriteLine("文件夹{0}的容量为{1}字节\n", (String)result.AsyncState, size);

     

                }

                catch (DirectoryNotFoundException e)

                {

                     Console.WriteLine(e.ToString());

                }

     

            }

    4.实现IAsyncResult异步调用模式的组件

    在.Net基类库中,有一些现在的组件直接实现了IAsyncResult异步调用设计模式,这些组件通常同时提供某个方法的同步与异步调用形式

     System.Net命名空间中WebRequest,下图展示了其中的方法,

    仔细分析会发现有一些规律

    1.有一个 Begin方法都有一个对应的End方法

    2.每组Begin/End方法都有一个对应的同步方法 如:BeginGetResponse/EndGetResponse  GetResponse()

    3.End方法与对应的同步方法返回值类型相同

    Begin方法返回一个IAsyncResult对像,而End方法的参数接收此对像,这种模式与基于委托的异步调用模式几乎一样

    示例代码见下方:实现功能,采用异步回调的方法通知用户文件下载完毕

    class Program

        {

            static void Main(string[] args)

            {

                string InputUrl = "";

                string FileName = "";

                Console.WriteLine("输入URL启动异步下载文件任务");

                do

                {

                    Console.WriteLine("输入WEB文件");

                    InputUrl = Console.ReadLine();

                    if (string.IsNullOrEmpty(InputUrl))

                    {

                        Console.WriteLine("URL不能输入空字符串");

                        continue;

                    }

                    Console.WriteLine("输入保存文件名");

                    FileName = Console.ReadLine();

                    if (string.IsNullOrEmpty(FileName))

                    {

                        Console.WriteLine("文件名不能输入空");

                        continue;

                    }

                    if (InputUrl == "quit" || FileName == "quit")

                    {

                        break;

                    }

                    try

                    {

                        Uri webUri = new Uri(InputUrl);

                        WebRequest webrequest = WebRequest.Create(webUri);

                        DownLoadTask d = new DownLoadTask { WebRequestObj = webrequest, SaveFileName = FileName };

                        Console.WriteLine("{0}已在后台启动下载保存为{1}",InputUrl, FileName);

                        webrequest.BeginGetResponse(DownLoadFinished, d);

                    }

                    catch (Exception ex)

                    {

                        Console.WriteLine(ex.ToString());

                    }

                } while (true);

            }

            static void DownLoadFinished(IAsyncResult obj)

            {

                DownLoadTask d = obj.AsyncState as DownLoadTask;

                WebResponse webresponse = d.WebRequestObj.EndGetResponse(obj);

                string filecontent="";

                using(StreamReader reader=new StreamReader (webresponse.GetResponseStream(),Encoding.GetEncoding("gb2312")))

                {

                    filecontent = reader.ReadToEnd();

                }

                using (StreamWriter write = new StreamWriter(new FileStream(d.SaveFileName, FileMode.Create), Encoding.GetEncoding("gb2312")))

                {

                    write.Write(filecontent);

                }

                MessageBox.Show(string.Format("{0}下载完成", d.SaveFileName));

            }

        }

      public  class DownLoadTask

        {

            public WebRequest WebRequestObj { get; set; }

            public string SaveFileName { get; set; }

        }

  • 相关阅读:
    SESSION与COOKIE的区别
    一位36岁程序员的困惑(转)
    COOKIE&&SESSION
    PHP递归实现层级树状展现数据
    小程序优化
    css层级
    组件封装
    webpack构建流程
    HTTP2.0
    vue中子组件修改父组件传入的值
  • 原文地址:https://www.cnblogs.com/75115926/p/3033285.html
Copyright © 2020-2023  润新知