zoukankan      html  css  js  c++  java
  • 《CLR Via C# 第3版》笔记之(二十一) 异步编程模型(APM)

    APM是.NET中异步编程模型的缩写(Asynchronous Programing Model)。

    通过异步编程,使得我们的程序可以更加高效的利用系统资源。

    主要内容:

    • 一个APM的例子
    • GUI中的APM
    • APM的优劣点
    • AMP使用中的注意事项 

    1. 一个APM的例子

    .Net中的异步模型非常完善,只要看到Begin***者End***方法。基本都是相对***方法的异步调用方式。

    (注:***是方法的名称)

    所以在.Net中实现一个异步调用是很方便的,下面用个小例子来演示一个异步操作。

    首先是同步的方式请求百度搜索10次。(分别搜索1,2,3。。。。10)

    public class CLRviaCSharp_21
    {
        static void Main(string[] args)
        {
            // 用百度分别检索1,2,3。。。9,共检索10次
            DateTime start = DateTime.Now;
            string strReq = "http://www.baidu.com/s?wd={0}";
            for (int i = 0; i < 10; i++)
            {
                var req = WebRequest.Create(string.Format(strReq, i));
                // 注意这里的GetResponse是同步方法
                var res = req.GetResponse();
    
                Console.WriteLine("检索 {0} 的结果已经返回!", i);
    
                res.Close();
            }
    
            Console.WriteLine("耗用时间:{0}毫秒", TimeSpan.FromTicks(DateTime.Now.Ticks - start.Ticks).TotalMilliseconds);
            Console.ReadKey(true);
        }
    }

    结果如下:总共消耗时间33秒多。

    image

    异步的方式如下:

    public class CLRviaCSharp_21
    {
        static DateTime start;
        static void Main(string[] args)
        {
            // 用百度分别检索1,2,3。。。9,共检索10次
            start = DateTime.Now;
            string strReq = "http://www.baidu.com/s?wd={0}";
            for (int i = 0; i < 10; i++)
            {
                var req = WebRequest.Create(string.Format(strReq, i));
                // 注意这里的BeginGetResponse就是异步方法
                var res = req.BeginGetResponse(ProcessWebResponse, req);
            }
    
            Console.ReadKey(true);
        }
    
        private static void ProcessWebResponse(IAsyncResult result)
        {
            var req = (WebRequest)result.AsyncState;
            string strReq = req.RequestUri.AbsoluteUri;
            using (var res = req.EndGetResponse(result))
            {
                Console.Write("检索 {0} 的结果已经返回!\t", strReq.Substring(strReq.Length - 1));
                Console.WriteLine("耗用时间:{0}毫秒", TimeSpan.FromTicks(DateTime.Now.Ticks - start.Ticks).TotalMilliseconds);
            }
        }
    
    }

    结果如下:总共消耗的时间应该是下面所有时间中最长的那个,即9.5秒左右。

    image

    2. GUI中的APM

    异步编程除了在服务端会大量应用,在有GUI的客户端也应用比较多(为了保证客户端的界面不会假死)。

    但是Winform或WPF程序中,改变界面元素状态只有通过UI线程,其他线程如果试图改变UI元素,就会抛出异常(System.InvalidOperationException)。

    using System;
    using System.Windows.Forms;
    using System.Net;
    
    public class CLRviaCSharp_21
    {
        static void Main(string[] args)
        {
            MyWindowsForm mf = new MyWindowsForm();
            mf.ShowDialog();
        }
    }
    
    internal class MyWindowsForm : Form
    {
        public MyWindowsForm()
        {
            Text = "Click in the window to start a web request";
            Width = 800;
            Height = 600;
        }
    
        protected override void OnMouseClick(MouseEventArgs e)
        {
            // 开始异步web请求
            Text = "web request initilized";
            var webreq = WebRequest.Create("http://www.baidu.com");
            webreq.BeginGetResponse(ProcessWebResponse, webreq);
            base.OnMouseClick(e);
        }
    
        private void ProcessWebResponse(IAsyncResult result)
        {
            var req = (WebRequest)result.AsyncState;
            using (var res = req.EndGetResponse(result))
            {
                // SynchronizationContext.Current.Post(updateUI, res);
                // 这里改变UI元素Form的Text属性,抛出了异常
                Text = "Content length: " + res.ContentLength;
            }
        }
    }

    那么其他线程如何改变UI元素呢。为了保证UI始终有响应,必须能够让其他线程也能改变UI元素。

    答案就在上面代码中抛异常的上面一句代码。利用SynchronizationContext的Current属性来获取UI线程的同步上下文信息。

    然后调用其Post方法,异步的更新UI元素。

    using System;
    using System.Windows.Forms;
    using System.Net;
    using System.Threading;
    
    public class CLRviaCSharp_21
    {
        static void Main(string[] args)
        {
            MyWindowsForm mf = new MyWindowsForm();
            mf.ShowDialog();
        }
    }
    
    internal class MyWindowsForm : Form
    {
        public MyWindowsForm()
        {
            Text = "Click in the window to start a web request";
            Width = 800;
            Height = 600;
        }
    
        protected override void OnMouseClick(MouseEventArgs e)
        {
            // 开始异步web请求
            Text = "web request initilized";
            var webreq = WebRequest.Create("http://www.baidu.com");
            webreq.BeginGetResponse(ProcessWebResponse, webreq);
            base.OnMouseClick(e);
        }
    
        private void ProcessWebResponse(IAsyncResult result)
        {
            var req = (WebRequest)result.AsyncState;
            using (var res = req.EndGetResponse(result))
            {
                SynchronizationContext.Current.Post(updateUI, res);
            }
        }
    
        private void updateUI(object state)
        {
            var res = (WebResponse)state;
            // 这里改变UI元素Form的Text属性, updateUI这个函数是由UI线程执行的
            Text = "Content length: " + res.ContentLength;
        }
    }

    这样的实现看着很繁琐,所以《CLR via C#》作者Jeffrey实现了一个小方法(SyncContextCallback)来简化这个步骤。

    using System;
    using System.Windows.Forms;
    using System.Net;
    using System.Threading;
    
    public class CLRviaCSharp_21
    {
        static void Main(string[] args)
        {
            MyWindowsForm mf = new MyWindowsForm();
            mf.ShowDialog();
        }
    }
    
    internal class MyWindowsForm : Form
    {
        public MyWindowsForm()
        {
            Text = "Click in the window to start a web request";
            Width = 800;
            Height = 600;
        }
    
        protected override void OnMouseClick(MouseEventArgs e)
        {
            // 开始异步web请求
            Text = "web request initilized";
            var webreq = WebRequest.Create("http://www.baidu.com");
            webreq.BeginGetResponse(SyncContextCallback(ProcessWebResponse), webreq);
            base.OnMouseClick(e);
        }
    
        private void ProcessWebResponse(IAsyncResult result)
        {
            var req = (WebRequest)result.AsyncState;
            using (var res = req.EndGetResponse(result))
            {
                // 这里改变UI元素Form的Text属性, 
                // 这次是在UI线程执行的,SyncContextCallback中获取了UI线程的同步上下文,
                // 然后用Post方法调用了ProcessWebResponse函数
                Text = "Content length: " + res.ContentLength;
            }
        }
    
        // 大牛Jeffrey实现的SyncContextCallback方法
        private static AsyncCallback SyncContextCallback(AsyncCallback callback)
        {
            SynchronizationContext sc = SynchronizationContext.Current;
            // 如果没有同步上下文,直接返回传入的东西
            if (sc == null) return callback;
    
            // 返回一个委托,这个委托将委托Post到捕捉到的SC中
            return asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult);
        }
    }

    3. APM的优劣点

    上面两个例子,可以看出APM的一些优势,第一个例子说明异步的效率高。第二个GUI的例子说明异步可以增强用户体验。

    其实APM的优势总结起来有以下几点:

    1. 资源利用率底(比如I/O并发读写时,线程不必等I/O完了就可以处理其他请求。这样几个线程就可以处理大量请求)
    2. 垃圾回收快(线程不用等待请求的操作完成就回线程池等待了,这时的线程在它们的栈顶,垃圾回收时遍历线程查找根比较快)
    3. 线程少,调试快(调试时,一旦遇到断点就会挂起所有线程。异步可以保证线程尽量少,所以调试时感觉会快)
    4. 使得GUI一直保持响应(上面的GUI例子可以说明)
    5. 并发执行下载,速度快(上面第一个例子可以说明)

    当然,任何技术都不是完美的,有其适用的一面,也有其不适用的一面。

    了解它的缺点,有时候甚至可以帮助我们更好的使用它。

    APM的劣势主要有以下几点:

    1. 必须将代码分成多个回调方法(执行流程不直观)
    2. 避免使用实参和局部变量(实参和局部变量在回调方法中无法访问,记住回调方法在另一个线程中)
    3. 许多C#构造无法使用,比如try/catch/finally,using等(因为没法在一个方法中开始try,再在它的回调方法中完成finally)
    4. 有些功能很难实现,比如多个并发操作协作进行,取消和超时(比如上面例子中的更新GUI元素也很麻烦)

    4. AMP使用中的注意事项

    使用APM时以下几点需要注意:

    1. 不要提早调用End***方法,在回调函数中再调用End***(如果提早调用,异步操作没有完成,End***会一直等待,此时相当于同步调用)
    2. 调用End***且仅调用一次(异步操作初始化时分配的某些资源,必须调用End***才能释放。第二次调用End***时结果不可预测)
    3. 使用相同的对象调用Begin***方法和End***方法
    4. 在Begin***方法和End***方法中使用ref,out,params标记时需要注意(具体实现方法以后有空再补些例子:))
    5. 不能取消异步I/O操作(启动I/O操作后,控制权交给了I/O相关硬件)
    6. Begin***方法返回引用类型,无法返回值类型
    7. 用于打开文件的Win32 API(CreateFile)暂时没有异步的版本
    8. FileStream类在创建时就决定了是异步还是同步的方式(创建式指定异步标记,即使调用Read,也是通过Sleep来模拟同步。同样,指定了同步标记,调用BeginRead也是模拟的异步)
  • 相关阅读:
    zbb20181205 springboot_aop
    zbb20181204 GIT RM -R --CACHED 去掉已经托管在GIT上的文件
    zbb20181129 eclipse查看jar乱码问题
    反编译
    zbb20181116 验证 map list 是否存在空 null 字段信息
    zbb20181109 git 服务器上的 Git
    zbb20181023 idea,IntelliJ中的main函数和System.out.println()快捷键
    zbb20181023 idea,IntelliJ IDEA 2017.3-2018.1 全系列汉化包
    struts2拦截器(四)
    struts2OGNL表达式(三)
  • 原文地址:https://www.cnblogs.com/wang_yb/p/2267790.html
Copyright © 2011-2022 走看看