使用 Task 簡化異步編程


.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 提供的緩沖查詢為例, 用戶現在地圖上選擇一個合適的點, 按照一定半徑查詢查詢緩沖區, 再查詢這個緩沖區內相關的建築物信息, 這個例子中, 我們需要與服務端進行兩次交互:

  1. 根據用戶選擇的點查詢出緩沖區;
  2. 查詢緩沖區內的建築物信息;

這個例子在 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 資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM