• C#基础——谈谈.NET异步编程的演变史


    前言

    C#5.0最重要的改进,就是提供了更强大的异步编程。C#5.0仅增加两个新的关键字:async和await。

    使用异步编程,方法调用是在后台运行(通常在线程或任务的帮助下),并且不会阻塞调用线程。

    本文将介绍3种不同模式的异步编程:异步模式,基于事件的异步模式和新增加的基于任务的异步编程模式(TAP)。TAP是利用async和await关键字来实现的。通过这里的比较,将认识到新的增加的基于任务的异步模式的真正优势。

    假设情景:我们需要进行一个耗时操作(这里使用webclient对象下载百度首页代码),接下来通过同步以及上面谈到的3种异步模式实现。

    同步调用

    创建一个控制台应用程序:

     
    static void Main(string[] args)
            {
                WebClient client = new WebClient();
                client.Encoding = Encoding.UTF8;
                string resp = client.DownloadString("http://www.baidu.com");
                Console.WriteLine(resp);
                //todo:其他操作
                Console.ReadKey();
            }

    运行上述代码,在执行DownloadString的时候,主线程会被阻塞,如果有用户界面,那么用户体验肯定不好,而DownloadString这个方法的执行速度取决于网速等原因,所以在这种情况下,使用异步调用就非常有必要了。

    异步模式

    实现异步模式定义BeginXXX方法和EndXXX方法。例如,如果有一个同步方法DownloadString,异步方法将转化成2个方法BeginDownloadString和EndDownloadString。BeginXXX异步方法接受同步方法的所有输入参数以及一个AsyncCallback参数(一个委托,在异步方法执行完调用),返回一个IAsyncResult对象,用于验证调用是否完成,EndXXX异步方法使用同步方法的所有输出参数,并以同步方法的返回类型来返回结果(简单来说就是获取异步执行方法的返回值,如果在异步执行的方法还没结束,则阻塞主线程直到异步方法执行结束)。

    WebClient并没有异步模式的实现,但是可以用HttpWebRequest类来代替(该类有BeginGetResponse和EndGetResponse):

     
    static void Main(string[] args)
            {
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://www.baidu.com");
                req.BeginGetResponse(ar =>
                {
                    HttpWebResponse resp = (HttpWebResponse)req.EndGetResponse(ar);
    
                    Console.WriteLine(resp.ContentType );
                    //todo:其他操作
                }, null);
                Console.WriteLine("这是主线程");
                
                Console.ReadKey();
            }

    如果非要用WebClient来实现异步模式,则需要用委托(只给代码不多做介绍了):

     
    static void Main(string[] args)
            {
               //定义委托实例
                Func<string, string> downloadString = url =>
                {
                    WebClient client = new WebClient();
                    client.Encoding = Encoding.UTF8;
                  return client.DownloadString(url);
                  
                };
                //异步执行委托
                downloadString.BeginInvoke("http://www.baidu.com", ar =>
                {
                    string resp = downloadString.EndInvoke(ar);
                    Console.WriteLine(resp);
                },null);
                Console.WriteLine("这是主线程");
                
                Console.ReadKey();
            }

    异步模式的优势是使用委托就能实现异步编程。不用改变程序的行为。也不会阻塞界面的操作。但是,使用异步模式的过程是非常复杂的(尤其在winform或wpf中涉及更新界面操作时)。幸运的是,.NET2.0推出了基于事件的异步模式,能很轻松更新界面。

    基于事件的异步模式

    基于事件的异步模式定义了一个带有“Async”后缀的方法。例如:对于同步方法DownloadString,WebClient类提供一个异步变体方法DownloadStringAsync。异步方法完成后,不是定义被调用的委托,而是定义事件。当异步方法DownloadStringAsync完成后,会直接调用DownloadStringCompleted事件,实现方式和上面差不多。但这时可以直接访问界面UI元素。

    在这里我们换一个winform项目(界面只包含一个label和一个button),点击button更新label值:

     
    private void button1_Click(object sender, EventArgs e)
            {
               
                WebClient client = new WebClient();
                client.Encoding = Encoding.UTF8;
                client.DownloadStringCompleted += (sender1, e1) =>
                {
                    string resp = e1.Result;
                    label1.Text = resp.Substring(10);
                };
                client.DownloadStringAsync(new Uri("http://www.baidu.com"));
                  
            }

    基于事件的异步模式的优势在于易于使用。但是,如果在自定义类中实现这个模式,就没那么简单了。方法还是有的,可以使用BackgroundWorker类来实现异步调用同步方法。BackgroundWorker类实现了基于事件的异步模式。还有,这种模式与同步方法调用相比,顺序颠倒了。调用异步方法之前,需要定义这个方法完成时发生什么。因此,下面进入异步编程的新世界,利用async和await关键字。

    基于任务的异步模式(推荐使用)

    这次先看代码:

     
    private async  void button1_Click(object sender, EventArgs e)
            {
               
                WebClient client = new WebClient();
                client.Encoding = Encoding.UTF8;
    
              string resp=await client.DownloadStringTaskAsync("http://www.baidu.com");
                label1.Text = resp.Substring(10);
    
            }

    在.NET4.5中,更新了WebClient类,提供了基于任务的异步模式。该模式定义了一个带有“Async”后缀的方法,并返回一个Task类型。由于WebClient类已经提供了一个带有“Async”后缀的方法,因此新方法名为DownloadStringTaskAsync。

    DownloadStringTaskAsync方法声明返回Task<string>类型。但是,不需要为DownloadStringTaskAsync方法返回的结果声明一个Task<string>类型的变量。只要声明一个string类型的变量,并且使用await关键字。await关键字不会阻塞完成其他任务的线程。当DownloadStringTaskAsync方法完成其后台处理后,UI线程会继续并从后台线程中获得结果,赋值给resp。同时,在await关键字的下一行代码会继续执行。

    现在,代码简单多了。没有阻塞,也不需要手工切回UI线程,这些都是自动实现的。代码顺序也和惯用的同步编程基本上一样了。

    转换异步模式

    并非.NET FrameWork的所有类在.NET 4.5中都引入了新的异步方法,有些类还是只提供了BeginXXX和EndXXX方法的异步模式,那怎么办?

    我们以上面用到的HttpWebResponse为例(假设它没有新的异步方法):Task类型的泛型参数Task<WebResponse>,定义了调用方法的返回值类型,FromAsync方法的前两个参数是委托类型,传入对应的BeginXXX和EndXXX,由于我们没有输入参数,因而也没有输入参数的对象状态,所有是null,代码如下:

     
    private async   void button1_Click(object sender, EventArgs e)
            {
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://www.baidu.com");
    
                HttpWebResponse resp = (HttpWebResponse)await Task<WebResponse>.Factory.FromAsync(req.BeginGetResponse, req.EndGetResponse, null);
    
            }
     

    之前一直对这块比较模糊,今天学了总结下,关于异步编程以及多线程还有好多东西要学,也很复杂,本文也就简单介绍到这了

  • 相关阅读:
    [转]jQuery知识总结
    sqlserver2008 函数1
    使用触发器生成流水号
    日期格式
    数据库正在使用,删除不了的问题
    continue 语句
    逻辑语句和函数
    ASP.NET中的随机密码生成
    相对路径
    “基类包括字段,但其类型与控件的类型不兼容”的解决方法
  • 原文地址:https://www.cnblogs.com/jameslif/p/3656097.html
Copyright © 2020-2023  润新知