• 同步 VS 异步


    同步请求资源

    请求msdn上的一个页面计算页面大小

    static void Main(string[] args)
    {
        string url = "https://docs.microsoft.com/zh-cn/dotnet/core/api/system";
        try
        {
            WebRequest request = WebRequest.Create(url);
            WebResponse response = request.GetResponse();
            using (var reader = new StreamReader(response.GetResponseStream()))
            {
                string text = reader.ReadToEnd();
                Console.WriteLine(FormatBytes(text.Length));
            }
        }
        catch (WebException e)
        {
        }
        catch (IOException e)
        {
        }
        catch (NotSupportedException e)
        {
        }
    }
    static string FormatBytes(long bytes)
    {
        string[] magnitudes = new string[] { "GB", "MB", "KB", "Bytes" };
        long max = (long)Math.Pow(1024, magnitudes.Length);
        var t1 = magnitudes.FirstOrDefault(magnitude => bytes > (max /= 1024)) ?? "0 Bytes";
        var t2 = ((decimal)bytes / (decimal)max);
        return string.Format("{1:##.##} {0}", t1, t2).Trim();
    }

    Ctrl+F5输出 

    闪烁两下后

    这里对资源的请求都是同步的,通俗易懂点就是一个步骤一个步骤的执行,任何一个步骤耗时较长都会阻塞上下文线程(这里就是主线程)

    使用C#5.0异步请求资源

    static void Main(string[] args)
    {
        string url = "https://docs.microsoft.com/zh-cn/dotnet/core/api/system";
        Task task = WriteWebRequestSizeAsync(url);
        while (!task.Wait(100))
        {
            Console.Write(".");
        }
    }
    static async Task WriteWebRequestSizeAsync(string url)
    {
        try
        {
            WebRequest request = WebRequest.Create(url);
            WebResponse response = await request.GetResponseAsync();
            using (var reader = new StreamReader(response.GetResponseStream()))
            {
                string text = await reader.ReadToEndAsync();
                Console.WriteLine(FormatBytes(text.Length));
            }
        }
        catch (WebException)
        {
    
        }
        //省略了一些catch块
    }

    这种写法在MVC中早就熟悉了,但是原理确不是很清楚,只知道这样不会阻塞UI,async方法会创建一个新的线程执行,await会阻塞上下文线程,一知半解隐藏着很可怕定时炸弹!

    TPL异步调用

    static void Main(string[] args)
    {
        string url = "https://docs.microsoft.com/zh-cn/dotnet/core/api/system";
        Task task = WriteWebRequestSizeAsync(url);
        try
        {
            while (!task.Wait(100))
            {
                Console.Write(".");
            }
        }
        catch (AggregateException excetion)
        {
            excetion = excetion.Flatten();
            try
            {
                excetion.Handle(innerExcetion =>
                {
                    ExceptionDispatchInfo.Capture(excetion.InnerException).Throw();
                    return true;
                });
            }
            catch (WebException ex)
            {
    
            }
            //省略了一些catch块
        }
    }
    static Task WriteWebRequestSizeAsync(string url)
    {
        StreamReader reader = null;
        WebRequest request = WebRequest.Create(url);
        Task task = request.GetResponseAsync()
            .ContinueWith(antecedent =>
            {
                WebResponse response = antecedent.Result;
                reader = new StreamReader(response.GetResponseStream());
                return reader.ReadToEndAsync();
            })
            .Unwrap()
            .ContinueWith(antecedent =>
            {
                if (null != reader)
                    reader.Dispose();
                string text = antecedent.Result;
    
                Console.WriteLine(FormatBytes(text.Length));
            });
        return task;
    }

    这个写法是在没有C#5.0之前,异步请求资源就是这么完成的,乍一看非常复杂,但是比较一下上面一种写法,它似乎是思路清晰的

    1.request.GetResponseAsync创建一个任务等待msdn服务器的响应

    2.拿到这个响应后,获得响应流,将流转为字符串

    3.接下来是Unwrap,这个应该是最难理解的了,实际上只有加上这句话以ContinueWith的思路写下去,下一步就是直接拿string了

    4.最后流已经转为字符串了,我们做个简单的计算就行了

    Unwrap的奥义

    public static Task<TResult> Unwrap<TResult>(this Task<Task<TResult>> task);

    从签名上来看,它是一个Task<Task<TResult>>类型的拓展方法,任务,带返回值的任务...晕了,别急,等下慢慢来

    回到刚刚代码看看有什么端倪

    1.GetResponseAsync()创建(意思和返回值一样,为了区分任务返回值)一个任务,具体类型Task<WebResponse>,这个任务结束返回一个WebResponse

     

     2.第一个ContinueWith调用者是一个Task<WebResponse>,形参是一个委托接受一个Task<WebReponse>,返回一个Task<string>(通过reader.ReadToEndAsync可以理解),返回值是一个Task没有问题,问题在它的泛型参数是什么,首先此处ContinueWith中的代码是在一个新的工作线程中运行的,我们把它想象成主线程(只是相对的一个环境),'主线程'要完成什么任务呢?,他要拿Task<WebResponse>的执行结果WebResponse(虽然这里可以肯定这个任务已经执行完成了,但是微软没有这么做),然后根据这个WebResponse在创建一个Task<string>,作为当前工作线程的返回值

     3.现在问题来了,我们得到的是一个Task<Task<string>>,我们可以通过调用两次Result获取这个string,但是在这个ContinueWith块中,只能保证外层的Task是执行完成的,所以第二个Result或阻塞上下文线程

    Task task = request.GetResponseAsync()
        .ContinueWith(antecedent =>
        {
            WebResponse response = antecedent.Result;
            reader = new StreamReader(response.GetResponseStream());
            return reader.ReadToEndAsync();
        })
        .ContinueWith(antecedent =>
        {
            var resTask = antecedent.Result;
            var resString = resTask.Result;
            if (null != reader)
            {
                string text = resString;
                Console.WriteLine(FormatBytes(text.Length));
            }
        });

     4.这个时候再回到Unwarp(),它就是脱去了外层的Task,得到的内层的任务上下文线程,并把它作为延续任务的主线程

     5.这里取出的Result就是string,计算并输出

     

    法器ILSpy

    通过ILSpy反编译后可以逐步查到,Unwarp()实际上就是保证了内层任务开始执行,并返回一个内层任务的执行环境(上下文线程)

  • 相关阅读:
    SQL语法结构
    MVC ViewBag不能使用在工程文件中添加引用
    Structs2+spring+hibernate整合
    java解析多层嵌套json字符串
    [转载] javamail腾讯企业邮箱发送邮件
    【转载】java加载properties文件的六种方法总结
    Linux上执行java文件——用Eclipse将Java源代码生成可执行文件
    ssm框架实现用户登录的拦截器和过滤器
    Sql Server设置用户只能查看并访问特定数据库
    Java操作Excel之POI:excel导出文件
  • 原文地址:https://www.cnblogs.com/cheesebar/p/6553310.html
Copyright © 2020-2023  润新知