【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)不給力致使的,好了該睡覺了.....晚安!老鐵們....

相關文章
相關標籤/搜索