聲明
本文歡迎轉載,原文地址:http://www.cnblogs.com/DjlNet/p/7522869.html
前言
我們接了上一篇的未完待續,接着我們的划重點之行.....哈哈
理解:LINQ中的延遲執行的流式傳輸和緩沖傳輸
通俗的來講就是先把數據准備好也就是取數據的邏輯已經編寫好了(也就是構建好了IEumerable
就短短的幾行代碼便實現了對大量日志的檢索、解析過濾,這得感謝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.ReadLines
與 File.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線程的上下文信息。
接着我們通過上面的代碼,看到變化點來看到好處以及C#關於異步編程怎么進化的哈,其中采用了三種不同的方式實現同一種需求,單從代碼量上面或者復雜度來說都是遞減的(因為這里環境是Winform所以要遵循兩個原則:1、不要在UI線程上執行耗時的操作 2、不要除了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關鍵字做了進一步提升,具體參考微軟文檔。好了,重點來了,接下來博主吶,就會開始研究框架框架框架(其實也一直有關注和學習,只是感覺不能出文記錄),注意是框架而不是架構哦,畢竟架構本身也是由很多框架組建起來的哦就好像基礎組建與微服務的關系一樣,主要是看看人家怎么設計框架的,然后才是代碼是怎么寫的.....。最后再說一句:**掌控自己,就是掌控敵人 --盲僧 **!!!
更新(關於優化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)不給力導致的,好了該睡覺了.....晚安!老鐵們....