[C#.NET][TPL] 利用 Task.Factory.FromAsync / TaskCompletionSource 將 APM 轉換成 TAP
假使,類別裡面已經開發了 APM(Asynchronous Programming Model) 非同步模型,也就是 BegingXXX / EndXXX 方法,如果你已經受不了 APM 的所帶來的壞味道,我們可以利用 TPL 組合 BegingXXX / EndXXX 方法,另起爐灶..
內文連結
使用 Task.Factory.FromAsync 改善 APM 非同步模型
使用 Task.Factory.FromAsync + Task.ContinueWith 改善 APM 非同步模型
APM 詳細建立方式可參考以下
http://www.dotblogs.com.tw/yc421206/archive/2012/10/25/78910.aspx
http://www.dotblogs.com.tw/yc421206/archive/2011/01/03/20540.aspx
有關 TAP 非同步建立步驟可參考以下
http://www.dotblogs.com.tw/yc421206/archive/2013/08/05/113433.aspx
ReadUrl 方法裡調用了BeginGetResponse / EndGetResponse,又利用 SynchronizationContext 處理跨執行緒 UI 更新,這段程式碼看起又臭又長,維護起來相當痛苦;APM 即使是相當方便的調用,但一旦無法跟UI邏輯切開難免還是會寫成一大包
private void ReadUrl(string url) { WebRequest webRequest = WebRequest.Create(url); WebResponse webResponse = null; Stream stream = null; webRequest.BeginGetResponse(ar => { try { webResponse = webRequest.EndGetResponse(ar); if (webResponse == null) return; stream = webResponse.GetResponseStream(); if (stream == null) return; var content = ""; if (webResponse.ContentType.ToLower().StartsWith("text/")) { var reader = new StreamReader(stream, System.Text.Encoding.UTF8); content = reader.ReadToEnd(); this._synchronizationContext.Post(_ => { label_Length.Text = "Content length: " + webResponse.ContentLength.ToString(); label_Content.Text = content; }, null); } } catch (WebException we) { this._synchronizationContext.Post(_ => { label_Length.Text = "Failed: " + we.GetBaseException().Message; }, null); } finally { if (stream != null) { stream.Close(); } if (webResponse != null) { webResponse.Close(); } if (webResponse == null || stream == null) { this._synchronizationContext.Post(_ => { label_Length.Text = "Content length: null"; label_Content.Text = "Empty"; }, null); } } }, null); }
調用 ReadUrl 方法
private void button_ReadURL_Click(object sender, EventArgs e) { this.ReadUrl("http://wintellect.com/"); }
同樣的再看另外一個例子,方法體裡面有 UI 業務邏輯,能切開嗎?
private void ReadFile(string fileName) { var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read); byte[] bufferArray = new byte[stream.Length]; stream.BeginRead(bufferArray, 0, bufferArray.Length, ar => { try { var result = stream.EndRead(ar); this._synchronizationContext.Post(_ => { label_Length.Text = "Content length: " + result; label_Content.Text = Encoding.UTF8.GetString(bufferArray); }, null); } finally { if (stream != null) stream.Dispose(); } }, null); }
調用方法private void button_ReadFile_Click(object sender, EventArgs e) { this.ReadFile("TextFile1.txt"); }
APM 很好,但要怎麼做才能更好?
改善步驟如下:
- 命名規則以 Async 為後綴
- 返回 Task 或是 Task<TResult>
- 調用 TaskFactory.FromAsync 或 TaskCompletionSource 方法
使用 Task.Factory.FromAsync 改善 APM 非同步模型:
上面的例子實在是太糟了,可以利用 Task.Factory.FromAsync 來將 APM 轉換成建立 TAP 非同步模型:
private Task<WebResponse> ReadUrlAsync(string url)
{
WebRequest webRequest = WebRequest.Create(url);
//return Task.Run(() => Task<WebResponse>.Factory.FromAsync(webRequest.BeginGetResponse, webRequest.EndGetResponse, null));
return Task<WebResponse>.Factory.FromAsync(webRequest.BeginGetResponse, webRequest.EndGetResponse, null);
}
使用 Task.Factory.FromAsync + Task.ContinueWith 改善 APM 非同步模型:
EndRead 預設是回傳 int ,在此動用 Task.ContinueWith 技巧讓方法體回傳 byte[],主要是強調技巧,在命名規則回傳型別還是不要差太多,不然會令人不知所措。
private Task<byte[]> ReadFileAsync(string fileName) { Stream stream = null; stream = new FileStream(fileName, FileMode.Open, FileAccess.Read); var buffer = new byte[stream.Length]; var mainTask = Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, buffer, 0, buffer.Length, null); var subTask = mainTask.ContinueWith(task => { var length = task.Result; return length > 0 ? buffer : null; }); return subTask; }
另一個例子:
private Task<WebResponse> ReadUrlAsync1(string url) { WebRequest webRequest = WebRequest.Create(url); var maskTask = Task.Factory.FromAsync<WebResponse>(webRequest.BeginGetResponse, webRequest.EndGetResponse, null, TaskCreationOptions.None); var subTask = maskTask.ContinueWith(task => { WebResponse webResponse = null; try { webResponse = task.Result; return webResponse; } catch (AggregateException ae) { throw; } }); return subTask; }
使用 TaskCompletionSource 改善 APM 非同步模型:
調用 TaskCompletionSource.SetResult 將結果取出。
改變 Task 狀態可調用以下三個方法
private Task<byte[]> ReadFileAsync1(string fileName) { Stream stream = null; stream = new FileStream(fileName, FileMode.Open, FileAccess.Read); var buffer = new byte[stream.Length]; var tcs = new TaskCompletionSource<byte[]>(); stream.BeginRead(buffer, 0, buffer.Length, ar => { try { var length = stream.EndRead(ar); tcs.SetResult(length > 0 ? buffer : null); } catch (Exception exc) { tcs.SetException(exc); } }, null); return tcs.Task; }
調用 ReadFileAsync 方法,使用 async / await 關鍵字, ReadFileAsync 加上 await ,會回傳 byte[]
private async void button_ReadFileAsync_Click(object sender, EventArgs e) { var result = await this.ReadFileAsync("TextFile1.txt"); this.label_Length.Text = "Content length: " + result.Length; this.label_Content.Text = Encoding.UTF8.GetString(result); }
調用 ReadUrlAsync 方法,使用 async / await 關鍵字,ReadUrlAsync 沒有加上 await,回傳 Taskprivate async void button_ReadUrlAsync_Click(object sender, EventArgs e) { var task = ReadUrlAsync("http://wintellect.com/"); await task; var webResponse = task.Result; if (webResponse.ContentType.ToLower().StartsWith("text/")) { var reader = new StreamReader(webResponse.GetResponseStream(), System.Text.Encoding.UTF8); this._synchronizationContext.Post(_ => { label_Length.Text = "Content length: " + webResponse.ContentLength.ToString(); label_Content.Text = reader.ReadToEnd(); }, null); } }
範例下載:https://dotblogsfile.blob.core.windows.net/user/yc421206/1308/201386124423465.zip
以上文章出自:http://www.dotblogs.com.tw/yc421206/archive/2013/08/06/113555.aspx
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET