.NET并發(fā)編程-異步編程模型上
時間撲面而來,我們終將釋懷
健康的活著,平靜的過著
開心的笑著,適當?shù)拿χ?/span>
本系列學習在.NET中的并發(fā)并行編程模式,實戰(zhàn)技巧
本小節(jié)開始學習任務編程模型。本系列保證最少代碼呈現(xiàn)量,雖然talk is cheap, show me the code被奉為圭臬,我的學習習慣是,只學習知識點,代碼不在當下立馬要用的時候不會認真去讀的,更何況在大多時候在手機閱讀更不順暢。
本小節(jié)介紹任務異步模型。作為開發(fā)人員,異步編程是對你技能集的一個重要補充。
1、異步編程模型APM
異步(asynchronous)操作是指請求開始不需要等待請求的返回,請求完成后再切換回來,期間可以做其他事情。就是不會閑著等待任何一個操作的完成。
并行主要和應用程序性能有關,它還可以利用現(xiàn)代多核計算機結構,增強對多線程CPU密集型工作,異步是并發(fā)的超集,主要面向I/O密集型操作,而不是CPU密集型操作。異步解決了延遲問題(任何需要很長時間才能運行的問題)。
2、.NET中的異步支持
2.1 Begin/End模式
原始的異步編程模式是將一個長時間運行的函數(shù)分為兩個部分,一部分負責啟動異步操作Begin,另一部分負責完成時回調End,稱之為Begin/End模型。以讀取文件為例的同步和異步編程如下所示
void ReadFileBlocking(string filePath, Action<byte[]> process){using (var fileStream = new FileStream(filePath, FileMode.Open,FileAccess.Read, FileShare.Read)){byte[] buffer = new byte[fileStream.Length];int bytesRead = fileStream.Read(buffer, 0, buffer.Length);process(buffer);}}//以下為異步讀取IAsyncResult ReadFileNoBlocking(string filePath, Action<byte[]> process){using (var fileStream = new FileStream(filePath, FileMode.Open,FileAccess.Read, FileShare.Read, 0x1000,FileOptions.Asynchronous)){byte[] buffer = new byte[fileStream.Length];var state = Tuple.Create(buffer, fileStream, process);return fileStream.BeginRead(buffer, 0, buffer.Length,EndReadCallback, state);}}void EndReadCallback(IAsyncResult ar){var state = ar.AsyncState as Tuple<byte[], FileStream, Action<byte[]>>;using (state.Item2) state.Item2.EndRead(ar);state.Item3(state.Item1);}
上面EndReadCallback就是讀取完成后的回調函數(shù),什么是回調,回調是一個用于加快程序速度的函數(shù)。異步編程使用回調創(chuàng)建新的線程來獨立地運行方法。在異步運行時,程序通過一個用于注冊另一個函數(shù)的延續(xù)的可重入函數(shù)來通知調用函數(shù)任何更新,包括失敗、取消、進度和完成。這個過程需要一些時間才能產生結果。
Begin/End模式,將開始和回調通知分離開,代碼看起來不像順序結構那么順眼。并且像上例state變量一樣,我們需要維護一個狀態(tài)以保證回調函數(shù)能訪問到。
2.2 基于事件的異步編程
EAP(Event-based Asynchronous Programming)基于事件的異步編程模式是從.NET2開始引入。用事件代替以往的回調,但仍然不能剝離方法調用和事件處理程序。
3、基于任務異步編程模型
TAP模型淘汰了APM和EAP,如果你是C#使用者,建議使用TAP模型。TAP為異步代碼提供了一種干凈的聲明式風格。
C#5.0開始,對象Task和Task<T>在關鍵字async和await的支持下,已經成為異步操作模型的主要組件。TAP模型只通過純粹關注語法方面就解決了回調問題,從而繞過了推理代碼中表達的事件序列出現(xiàn)的困難。C#5.0中的異步函數(shù)解決了耗費運行時間的延遲問題。
TAP核心思想就是將異步操作包進一個Task里面。異步執(zhí)行的話,使用async關鍵字通知編譯器該方法異步運行不阻塞。上面讀取文件例子改為TAP模型如下
async Task ReadFileNoBlockingAsync(string filePath, Func<byte[], Task> process){using (var fileStream = new FileStream(filePath, FileMode.Open,FileAccess.Read, FileShare.Read, 0x1000,FileOptions.Asynchronous)){byte[] buffer = new byte[fileStream.Length];int bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length);await process(buffer);}}
當運行到await關鍵字時,它會掛起調用方法并將控制權交換給它的調用者,直到所等待的任務完成為止。
3.1 異步取消
以下是用于取消Task或異步操作相關的.NET類型:
CancellationTokenSource負責創(chuàng)建取消令牌,并向該令牌的所有拷貝發(fā)送取消請求
CancellationToken是用于監(jiān)控當前令牌狀態(tài)的結構。
TAP模型中的取消支持,創(chuàng)建任務時傳遞取消令牌,然后異步操作會檢查令牌的狀態(tài),并在觸發(fā)請求時取消計算。像下面所示:
CancellationTokenSource cts = new CancellationTokenSource(); // 取消令牌async Task<string> DownloadStockHistory(string symbol,CancellationToken token) //傳遞{string stockUrl = CreateFinanceUrl(symbol);var request = await new HttpClient().GetAsync(stockUrl, token); // 傳遞給方法以得取消return await request.Content.ReadAsStringAsync();}//cts.Cancel(); // 出發(fā)取消令牌
或則使用CancellationToken來注冊回調
CancellationTokenSource tokenSource = new CancellationTokenSource();CancellationToken token = tokenSource.Token;Task.Run(async ()=> {var webClint = new WebClient();token.Register(()=>webClint.CancelAsync());var data = await webClint.DownloadDataTaskAsync(url);},token);
在之前版本如果沒有async和await時,通過手動檢查取消,定期使用token.ThrowIfCancellationRequested方法檢查取消
3.2 協(xié)作取消支持
使用CancellationTokenSource可以輕松創(chuàng)建由多個其他令牌組成的組合令牌。像下面所示有一個取消原因被觸發(fā)就執(zhí)行取消操作。
CancellationTokenSource ctsOne = new CancellationTokenSource()CancellationTokenSource ctsTwo = new CancellationTokenSource();CancellationTokenSource ctsComposite = CancellationTokenSource.CreateLinkedTokenSource(ctsOne.Token,ctsTwo.Token);CancellationToken token = ctsComposite.Token;
字數(shù)有點超了哈,想要每個小片1k字左右的,剩下的異步重試,錯誤處理放在下小節(jié)吧。
時間撲面而來,我們終將釋懷
健康的活著,平靜的過著
開心的笑著,適當?shù)拿χ?/span>
