异步编程模式

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

    }

转载于:https://www.cnblogs.com/75115926/archive/2013/04/20/3033285.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值