非同步CTP(Async CTP)為什麼那樣工作?

tkbSimplest發表於2015-09-26
對非同步CTP感興趣有很多原因。非同步CTP使非同步程式設計比以前更加容易了。它雖然沒有Rx強大,但是更容易學。非同步CTP介紹了兩個新的關鍵字,async和await。非同步方法(或Lambda表示式)必須返回void,Task或Task<TResult>。這篇文章不是介紹非同步CTP的,因為網上有很多這樣的文章。這篇文章的目的是把程式設計師開始使用Async CTP遇到的一些常見問題集中起來。

推斷返回型別

當從非同步方法返回一個值的時候,此方法體直接返回這個值,但該方法本身被宣告為返回一個Task<TResult>。當宣告一個返回甲型別的方法卻必須返回一個乙型別時,就有點“斷連”了。

// 實際語法
public async Task<int> GetValue()
{
  await TaskEx.Delay(100);
  return 13; //返回型別是 "int", 而不是"Task<int>"
}

 

問題來了:為什麼我不能這麼寫?

// 假想語法
public async int GetValue()
{
  await TaskEx.Delay(100);
  return 13; // 返回型別是 "int"
}

 

思考:該方法如何如何照顧呼叫者呢?非同步方法必須返回一個實際結果型別Task<TResult>的值。因此,GetValue方法會出現返回Task<TResult>的智慧提示(在物件瀏覽器,Reflector等中也是這樣的)。
 
在設計之初,推斷返回型別已經被考慮到了,但該設計團隊已經推斷出在非同步方法中保持這種“斷連”比在程式碼基上擴大這種“斷連”更好。如今這種“斷連”仍存在,但比以前更小了。該設計團隊的共識是一致的方法簽名更佳。
思考:async void 和async Task有什麼區別?
一個async Task方法就像任何其他的非同步操作一樣,只是沒有返回值。一個async void方法扮演一種高階操作。async Task方法可能被組合進其他使用using await的非同步方法。async void方法可能被用作一個事件控制程式碼。async void方法也有其他重要的屬性:在ASP.NET上下文中,它通知web伺服器直到它返回,頁面才完成。
 
推斷返回型別會移除async void 和async Task間的區別:要麼所有的非同步方法是async void(阻止可組合性),要麼都是async Task(阻止它們來自事件控制程式碼,同時對ASP.NET要有一個可選擇的方案)。

 非同步返回

 
在方法宣告返回型別和方法體返回的型別之間仍有“斷連”。該設計團隊的另一個建議是:給return新增一個關鍵字來指示return返回的值,但這個也確實沒有返回什麼,如下所示:
// 假想語法
public async Task<int> GetValue()
{
  await TaskEx.Delay(100);
  async return 13; // "async return" 意味著值被包裝在Task中
}

 

思考:將大量的程式碼從同步轉為非同步。

async return關鍵字也被考慮到了,但並沒有足夠的說服力。當把一些同步程式碼轉成非同步程式碼時,這尤其正確。強制人們給每個return語句新增asynchronous就好像是“不必要的忙碌”。比較而言,習慣於“斷連”更容易。

推斷“async”

async關鍵字必須用在使用了await關鍵字的方法上。然而,如果把async用在了一個沒有使用await的方法上,也會收到一個警告。

問題:為什麼async不能根據await的存在推斷出來?

//假想語法
public Task<int> GetValue()
{
  // "await" 的存在暗示這是一個 "async" 方法.
  await TaskEx.Delay(100);
  return 13;
}

思考:向後相容性和程式碼可讀性

單字的await關鍵字具有太大的打破變化。在非同步方法上的多字await(如await for)或一個關鍵字之間的選擇,只是在那個方法內部啟用await關鍵字。很明顯,使用async標記方法讓人類和計算機分析起來更容易,因此設計團隊決定使用async/await對。

推斷“await”

問題:既然顯示包括async有意義(看上面),為什麼await不能根據async的存在推斷出來呢?

// 假想語法
public async Task<int> GetValue()
{
  // 暗示有"await",因為這是一個 "async" 方法.
  TaskEx.Delay(100);
  return 13;
}

思考:非同步操作的並行組合。

乍一看,推斷await推斷似乎簡化了基本的非同步操作。只要所有的等待可以按序列(如一個操作等待,然後另一個,再然後另一個)完成,這個就能很好的工作。然而,當有人考慮並行組合的時候,它崩潰了。

非同步CTP中的並行組合使用TaskEx.WhenAny 和TaskEx.WhenAll方法。這有一個簡單的例子,這個方法立即開始了兩個操作,並且等待它們完成。

// 實際語法
public async Task<int> GetValue()
{
  // 非同步檢索兩個部分的值
  // 注意此時它們是沒有等待的“not await”
  Task<int> part1 = GetValuePart1();
  Task<int> part2 = GetValuePart2();

  // 等待它們的值到達。
  await TaskEx.WhenAll(part1, part2);

  // 計算我們的結果
  int value1 = await part1; // 實際上沒有等待
  int value2 = await part2; //實際上沒有等待
  return value1 + value2;
}

為了處理並行組合,我們必須有能力說我們將不會await一個表示式。

相關文章