zoukankan      html  css  js  c++  java
  • 异步编程模式

    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; }

        }

  • 相关阅读:
    Algorithm Gossip (48) 上三角、下三角、对称矩阵
    .Algorithm Gossip (47) 多维矩阵转一维矩阵
    Algorithm Gossip (46) 稀疏矩阵存储
    Algorithm Gossip (45) 费氏搜寻法
    Algorithm Gossip (44) 插补搜寻法
    Algorithm Gossip (43) 二分搜寻法
    Algorithm Gossip (42) 循序搜寻法(使用卫兵)
    Algorithm Gossip (41) 基数排序法
    Algorithm Gossip (40) 合并排序法
    AlgorithmGossip (39) 快速排序法 ( 三 )
  • 原文地址:https://www.cnblogs.com/75115926/p/3033285.html
Copyright © 2011-2022 走看看