【C# in depth 第三版】温故而知新(2)

声明


本文欢迎转载原文地址:http://www.cnblogs.com/DjlNet/p/7522869.htmlhtml


前言


咱们接了上一篇的未完待续,接着咱们的划重点之行.....哈哈web


理解:LINQ中的延迟执行的流式传输和缓冲传输

通俗的来说就是先把数据准备好也就是取数据的逻辑已经编写好了(也就是构建好了IEumerable 可迭代的数据流),可是只是准备好还没加载到内存中,而后在恰当的位置以一种“just-in-time”的方式提供(也就是在触发MoveNext的才去真的取出数据),这就是称为延迟执行。LINQ框架中老是尽可能采用流式传输,在调用MoveNext的时候从迭代器中取出一个元素Current项,而后执行处理相似Where或者Cast,而后返回结果,这样一来就较少的占用了存储空间;在某些状况下又不得不采用缓冲传输,好比反转Reverse或者排序OrderBy啥的,就要求数据所有处于可用的状态也就是加载到内存中来执行批处理 。类比一下就是流式传输就好像DataReader来每次处理一条记录同样,而后缓冲传输就貌似DataSet整个读取数据同样。(其中流式传输也称为惰性求值,缓冲传输也称为热情求值,它们都属于延迟执行,与其相反的是当即执行,相似返回一个单一的值Max或者ToList之类什么的,从天然的角度来看也是符合人之常情能够理解的说法),再说说Join中延迟执行右边的数据将会被缓冲处理,而左边的数据依然会进行流式处理,因此这就是为何尽可能join对象的数据量尽可能小一些的缘由,那么同理在在数据库中上述的道理依然行得通。接着咱们举个列子来讲明延迟执行的好处,这里咱们须要遍历一个Logs日志目录递归下面全部的文件的内容,找出Error对应的行内容,注意这里不会一次性加载一个日志文件全部内容,更也不会加载目下下面的全部文件内容,这里就是依赖了框架提供了流式API的调用,其实看图中的标记便可知道:

就短短的几行代码便实现了对大量日志的检索、解析过滤,这得感谢LINQ的流式处理。关于上述代码的红框部分(1)Directory.GetFiles 以及 Directory.EnumerateFiles 两个API之间的区别看名字就一目了然了吧,这里引用一下官方回答:数据库

The EnumerateFiles and GetFiles methods differ as follows: When you use EnumerateFiles, you can start enumerating the collection of names before the whole collection is returned; when you use GetFiles, you must wait for the whole array of names to be returned before you can access the array. Therefore, when you are working with many files and directories, EnumerateFiles can be more efficient.编程

(2)File.ReadLinesFile.ReadAllLines 之间的区别从返回值也能够明显的分别出来了,一个是流式加载一个当即加载,道理如同上述的第一点(1)
这样一来也一样说明了,为何框架老是尽可能尝试以一种流式的方式处理数据集,这也是为何咱们须要返回IEnumerable 的缘由了。 网络


理解:Async / Await 异步编程浅析

这里呢,园子不少好文章都已经解释了怎么用呀,什么大体原理,什么状态机什么的,我这里就仍是引用书中的说辞来通俗的说说,在没有C#5这么安逸的异步编程以前以后的带来的感觉,举个小例子看C#团队帮咱们干了什么好事儿。

不用在乎界面,下面把完整代码贴出来:架构

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            _synchronizationContext = SynchronizationContext.Current;
        }

        private static readonly HttpClient _httpClient = new HttpClient();
        private static readonly WebClient _webClient = new WebClient();
        private readonly SynchronizationContext _synchronizationContext;
        private const string _url = "http://www.bing.com";

        /// <summary>
        /// ThreadPool方式构建异步
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            this.button1.Enabled = false;
            ThreadPool.QueueUserWorkItem(x =>
            {
                try
                {
                    var result = _webClient.DownloadString(_url);
                    _synchronizationContext.Post(length =>
                    {
                        int temp = Convert.ToInt32(length);
                        this.label4.Text = temp.ToString();
                    }, result.Length);
                }
                catch (Exception exception)
                {
                    // 这里通过测试可使用静态方法Show,原理应该也是把消息写进Winform的消息泵中,由WinForm框架自身去循环调度触发
                    MessageBox.Show(exception.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);

                    // 同理下面的代码依然能够
                    //_synchronizationContext.Post(msg =>
                    //{
                    //    var message = msg as string;
                    //    MessageBox.Show(message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    //}, exception.Message);
                }
                finally
                {
                    // 测试除UI线程以外的线程访问UI控件异常
                    //this.button1.Enabled = true;

                    _synchronizationContext.Post(empty =>
                    {
                        this.button1.Enabled = true;
                    }, null);
                }
            });
        }

        /// <summary>
        /// Task构建异步
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            this.button2.Enabled = false;

            Task.Factory.StartNew<string>(x =>
            {
                var result = _webClient.DownloadString(_url);
                return result.Length.ToString();
            }, null)
            .ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    // faulted with exception
                    Exception ex = task.Exception;
                    while (ex.InnerException != null)
                        ex = ex.InnerException;
                    MessageBox.Show("Error: " + ex.Message);
                }
                else if (task.IsCanceled)
                {
                    // this should not happen 
                    // as you don't pass a CancellationToken into your task
                    MessageBox.Show("Canclled.");
                }
                else
                {
                    // completed successfully
                    if (!string.IsNullOrWhiteSpace(task.Result))
                    {
                        _synchronizationContext.Post(length =>
                        {
                            int temp = Convert.ToInt32(length);
                            this.label3.Text = temp.ToString();
                        }, task.Result);
                    }
                    //MessageBox.Show("Result: " + task.Result);
                }

                _synchronizationContext.Post(empty =>
                {
                    this.button2.Enabled = true;
                }, null);

                //// 测试除UI线程以外的线程访问UI控件异常
                ////this.button2.Enabled = true;
            }, TaskContinuationOptions.ExecuteSynchronously);
        }

        /// <summary>
        /// Async/Await构建异步
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void button3_Click(object sender, EventArgs e)
        {
            this.button3.Enabled = false;
            try
            {
                string temp = await _httpClient.GetStringAsync(_url);
                this.label6.Text = temp.Length.ToString();
            }
            catch (Exception exception)
            {
                while (exception.InnerException != null)
                {
                    exception = exception.InnerException;
                }
                MessageBox.Show(exception.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                this.button3.Enabled = true;
            }
        }
    }
}

其中这里还需对上面代码 SynchronizationContext 说明一下:正是由于有了它,咱们的异步async/await异步函数的后续操做,可以正确的回到UI线程执行(前提是ConfigureAwait(continueOnCaptureedContext : true) 显示的捕获调用者的上下文这里就是UI线程上下文,其中该方法默认也是参数:true ),其实这个玩意儿已经在.NET 2.0都已经有了,当时是为了提供给 BackgroundWork等组件使用,SynchronizationContext 保证了在适当的线程执行委托的概念,以致于咱们调用 SynchronizationContext.Post(异步)或者 SynchronizationContext.Send (同步) 发送消息,与在 Winform中的 Control.BeginInvoke和Control.Invoke有殊途同归之妙呀!!!得注意一点就是在不一样的环境模型中,同步上下文表明的含义是不一致的咯!!!这里的咱们代码中的 SynchronizationContext 就表明了 UI线程的上下文信息。app

接着咱们经过上面的代码,看到变化点来看到好处以及C#关于异步编程怎么进化的哈,其中采用了三种不一样的方式实现同一种需求,单从代码量上面或者复杂度来讲都是递减的(由于这里环境是Winform因此要遵循两个原则:一、不要在UI线程上执行耗时的操做 二、不要除了UI线程以外的其余线程访问UI控件,因此代码略多了点),不过从理解上面都仍是比较好理解,毕竟代码上面你们一看就应该是知道底层套路都同样,可是从体验或者感觉其中包含了异常处理、线程切换自动回到正确的上下文等仍是Async/Await的方式最舒服,虽然到了Task的时候有ContinueWith来衔接任务能够解决回调地狱的问题(毕竟ThreadPool可怜的尚未回调机制)。框架

await 的主要目的是等待耗时操做是能够避免阻塞,当方法执行到 await 表达式就返回了,当完成等待以后,后续操做依然能够回到原先的UI线程去执行,看到这里有木有一种感受就是async/await已经帮咱们把咱们本身的手动实现都作好了并且作得更好作得更多,那是由于C#编译器会对全部await都构建一个后续操做,这里后续操做对于咱们来就是就是this.label6.Text = temp.Length.ToString();。关于更加详细的解读,以及内部状态机的构造和状态管理等就是比较复杂了,这里博主也不是很清楚,详情参考官方文档或者博客呗以及书中的详解篇幅也是有的,其实通常状况也不须要关心内部构造,须要关心如何去最佳实践便可。异步

小总结

到这里第二篇文章也差很少了,这本书的划重点也差很少了(我的来看的话,其实呢可能还有其余忽略的地方,后面CLR温故的时候再补充也是能够的),其实再看了第二遍这本书呐,给我最大感觉仍是对书中某些模棱两可的知识可能更加稍微掌握了些,还有就是在C#发展的里程碑中,在功能性和体验性上面来讲,我的以为仍是 LINQ、Async/Await 带来的东西是给开发者最好的礼物,简直就是其余语言模仿或者学习的标杆(原谅博主活在C#的温柔乡中......),哈哈,固然了好的语言设计那确定是要分享的嘛,否则其余开发者岂不是很难受!!并且在后面的C#6中对异步编程的await关键字作了进一步提高,具体参考微软文档。好了,重点来了,接下来博主呐,就会开始研究框架框架框架(其实也一直有关注和学习,只是感受不能出文记录),注意是框架而不是架构哦,毕竟架构自己也是由不少框架组建起来的哦就好像基础组建与微服务的关系同样,主要是看看人家怎么设计框架的,而后才是代码是怎么写的.....。最后再说一句:掌控本身,就是掌控敌人 --盲僧 !!!async

更新(关于优化Task构建异步 2017年9月19日00:53:55)

/// <summary>
        /// Task构建异步优化
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button4_Click(object sender, EventArgs e)
        {
            this.button4.Enabled = false;

            var task1 = Task.Factory.StartNew<string>(x =>
            {
                var result = _webClient.DownloadString(_url);
                return result.Length.ToString();
            }, null);

            var task2 = task1.ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    // faulted with exception
                    Exception ex = task.Exception;
                    while (ex.InnerException != null)
                        ex = ex.InnerException;
                    MessageBox.Show("Error: " + ex.Message);
                }
                else if (task.IsCanceled)
                {
                    // this should not happen 
                    // as you don't pass a CancellationToken into your task
                    MessageBox.Show("Canclled.");
                }
                else
                {
                    // completed successfully
                    if (!string.IsNullOrWhiteSpace(task.Result))
                    {
                        this.label8.Text = task.Result;
                    }
                }

                this.button4.Enabled = true;

            }, TaskScheduler.FromCurrentSynchronizationContext());
        }

优化说明:删除手动使用同步上下文去控制UI元素,而使用了关键的TaskScheduler.FromCurrentSynchronizationContext()来自动使用当前的同步上下文,方法说明:建立一个与当前 System.Threading.SynchronizationContext 关联的 System.Threading.Tasks.TaskScheduler,其实折腾这玩意儿为了啥,也就是为了也能在.Net4.0的环境也就是客户端电脑还处于这个时期的时候,可以正确是姿式编写异步代码且不那么难受就行了,至于说可使用一个nuget包Microsoft.Bcl.Async 还没有尝试过,道听途说有点小问题没亲测,不过目前来看应该还能够(瞎猜),主要是客户端的电脑人家是win7安装默认也是net4.0,可是呐他们又不想卡主界面致使未响应,其实也是数据库和网络(异地跨国调用,摊手.jpg)不给力致使的,好了该睡觉了.....晚安!老铁们....

相关文章
相关标签/搜索