.Net 傳統非同步編程概述 .NET Framework 提供以下兩種執行 I/O 綁定和計算綁定非同步操作的標準模式: 非同步編程模型 (APM),在該模型中非同步操作由一對 Begin/End 方法(如 FileStream.BeginRead 和 Stream.EndRead)表示。 基於事件的非同步 ...
.Net 傳統非同步編程概述
.NET Framework 提供以下兩種執行 I/O 綁定和計算綁定非同步操作的標準模式:
- 非同步編程模型 (APM),在該模型中非同步操作由一對 Begin/End 方法(如 FileStream.BeginRead 和 Stream.EndRead)表示。
- 基於事件的非同步模式 (EAP),在該模式中非同步操作由名為“操作名稱Async”和“操作名稱Completed”的方法/事件對(例如 WebClient.DownloadStringAsync 和 WebClient.DownloadStringCompleted)表示。 (EAP 是在 .NET Framework 2.0 版中引入的)。
Task 的優點以及功能
通過使用 Task 對象,可以簡化代碼並利用以下有用的功能:
- 在任務啟動後,可以隨時以任務延續的形式註冊回調。
- 通過使用 ContinueWhenAll 和 ContinueWhenAny 方法或者 WaitAll 方法或 WaitAny 方法,協調多個為了響應 Begin_ 方法而執行的操作。
- 在同一 Task 對象中封裝非同步 I/O 綁定和計算綁定操作。
- 監視 Task 對象的狀態。
- 使用 TaskCompletionSource 將操作的狀態封送到 Task 對象。
使用 Task 封裝常見的非同步編程模式
1、 使用 Task 對象封裝 APM 非同步模式, 這種非同步模式是 .Net 標準的非同步模式之一, 也是 .Net 最古老的非同步模式, 自 .Net 1.0 起就開始出現了,通常由一對 Begin/End 方法同時出現, 以 WebRequest 的 BeginGetResponse 與 EndGetResponse 方法為例:
var request = WebRequest.CreateHttp(UrlToTest); request.Method = "GET"; var requestTask = Task.Factory.FromAsync<WebResponse>( request.BeginGetResponse, request.EndGetResponse, null ); requestTask.Wait(); var response = requestTask.Result;
2、使用 Task 對象封裝 EPM 非同步模式, 這種模式從 .Net 2.0 開始出現, 同時在 Silverlight 中大量出現, 這種非同步模式以 “操作名稱Async” 函數和 “操作名稱Completed” 事件成對出現為特征, 以 WebClient 的 DownloadStringAsync 方法與 DownLoadStringCompleted 事件為例:
var source = new TaskCompletionSource<string>(); var webClient = new WebClient(); webClient.DownloadStringCompleted += (sender, args) => { if (args.Cancelled) { source.SetCanceled(); return; } if (args.Error != null) { source.SetException(args.Error); return; } source.SetResult(args.Result); }; webClient.DownloadStringAsync(new Uri(UrlToTest, UriKind.Absolute), null); source.Task.Wait(); var result = source.Task.Result;
3、 使用 Task 對象封裝其它非標準非同步模式, 這種模式大量出現在第三方類庫中, 通常通過一個 Action 參數進行回調, 以下麵的方法為例:
void AddAsync(int a, int b, Action<int> callback)
封裝方法與封裝 EPM 非同步模式類似:
var source = new TaskCompletionSource<int>(); Action<int> callback = i => source.SetResult(i); AddAsync(1, 2, callback); source.Task.Wait(); var result = source.Task.Result;
通過上面的例子可以看出, 用 Task 對象對非同步操作進行封裝之後, 非同步操作簡化了很多, 只要調用 Task 的 Wait 方法, 可以直接獲取非同步操作的結果, 而不用轉到回調函數中進行處理, 接下來看一個比較實際的例子。
緩衝查詢示例
以 Esri 提供的緩衝查詢為例, 用戶現在地圖上選擇一個合適的點, 按照一定半徑查詢查詢緩衝區, 再查詢這個緩衝區內相關的建築物信息, 這個例子中, 我們需要與服務端進行兩次交互:
- 根據用戶選擇的點查詢出緩衝區;
- 查詢緩衝區內的建築物信息;
這個例子在 GIS 查詢中可以說是非常簡單的, 也是很典型的, ESRI 的例子中也給出了完整的源代碼, 這個例子的核心邏輯代碼是:
_geometryService = new GeometryService(GeoServerUrl); _geometryService.BufferCompleted += GeometryService_BufferCompleted; _queryTask = new QueryTask(QueryTaskUrl); _queryTask.ExecuteCompleted += QueryTask_ExecuteCompleted; void MyMap_MouseClick(object sender, Map.MouseEventArgs e) { // 部分代碼省略, 開始緩衝查詢 _geometryService.BufferAsync(bufferParams); } void GeometryService_BufferCompleted(object sender, GraphicsEventArgs args) { // 部分代碼省略, 獲取緩衝查詢結果, 開始查詢緩衝區內的建築物信息 _queryTask.ExecuteAsync(query); } void QueryTask_ExecuteCompleted(object sender, QueryEventArgs args) { // 將查詢結果更新到界面上 }
這隻是一個 GIS 開發中很簡單的一個查詢, 上面的代碼卻將邏輯分散在三個函數中, 在實際應用中, 與服務端的交互次數會更多, 代碼的邏輯會分散在更多的函數中, 導致代碼的可讀性以及可維護性降低。 如果使用 Task 對象對這些任務進行封裝, 那麼整個邏輯將會簡潔很多, GeometryService 和 QueryTask 提供的是 EPM 非同步模式, 相應的封裝方法如上所示, 最後, 用 Task 封裝非同步操作之後的代碼如下:
void MyMap_MouseClick(object sender, Map.MouseEventArgs e) { Task.Factory.StartNew(() => { // 省略部分 UI 代碼, 開始緩衝查詢 var bufferParams = new BufferParameters() { /* 初始化緩衝查詢參數 */}; var bufferTask = _geometryService.CreateBufferTask() // 等待緩衝查詢結果 bufferTask.Wait(); // 省略更新 UI 的代碼, 開始查詢緩衝區內的建築物信息 var query = new Query() { /* 初始化查詢參數 */ }; var queryExecTask = _queryTask.CreateExecTask(query); queryExecTask.Wait(); // 將查詢結果顯示在界面上, 代碼省略 }); }
從上面的代碼可以看出, 使用 Task 對象可以把原本分散在三個函數中的邏輯集中在一個函數中即可完成, 代碼的可讀性、可維護性比原來增加了很多。
Task 能完成的任務遠不止這些,比如並行計算、 協調多個併發任務等, 有興趣的可以進一步閱讀相關的 MSDN 資料。