2019年2月6日 星期三

Task.FromResult 用法與注意事項

Task.FromResult 用法與注意事項
Task 類別有一個靜態方法可用來傳回已完成的工作,這個方法是 FromResult()。
底下是一個簡單範例:


Task<int>task1=Task.FromResult(10);


什麼時候會用到它呢?
一個常見的應用場合是:在非同步方法中,希望以同步的方式傳回結果
舉例來說,假設你現在要設計一個支援非同步處理的快取物件,你先定義了如下介面:


Interface IMyCache
{
Task<string>GetDataAsync();
}


這表示,你預期將來實作 IMyCache 介面時,GetDataAsync 會是個非同步方法。
然而,在某種特殊情況下(例如撰寫單元測試),你可能不需要複雜的非同步操作,
而只需要直接傳回一個現成的結果。那麼,在撰寫 GetDataAsync 方法時,
便可以用上 Task.FromResult。如下所示:


Class MyCache : IMyCache
{
Public async Task<string>GetDataAsync()
{
 Return await Task.FromResult("hello");
 // 此寫法有點問題,稍後說明。
}
}


以下程式碼則示範了如何在 Console 應用程式中使用 MyCache 類別的 GetDataAsync 方法:


Static void Main(string[] args)
{
 Task.Run(async() =>
  {
   IMyCache myCache =new MyCache();
   Var s = await myCache.GetDataAsync();
   System.Console.WriteLine(s);
  });
Console.ReadLine();
}


等等,上述範例的寫法其實有個問題:在沒有非同步操作的地方多餘地使用了
async 和 await 關鍵字。接著要繼續討論這個問題

不要 await Task.FromResult 呼叫


假設你就是負責撰寫 MyCache 類別的人,那麼,你自然知道你寫的 GetDataAsync 方法
其實並沒有執行任何非同步工作,因為它是呼叫
Task.FromResult 來直接(立刻)傳回一個已經完成的工作
然而,剛才的 GetDataAsync 方法卻使用了 async 和 await 關鍵字,
而這兩個關鍵字會使編譯器產生一些額外的程式碼,以便用來處理非同步呼叫的等待以
及環境切換等處理。既然 Task.FromResult 會直接傳回現成的 Task 物件,那麼編譯器
所產生的那些額外的程式碼也就都是多餘的了


簡單地說,我們應該遵循這個建議:不要 await 一個 Task.FromResult 呼叫。
因此,先前的 GetDataAsync 方法應該把 async 和 await 去掉,變成這樣:


Public Task<string>GetDataAsync()
{
Return Task.FromResult("hello");
}


你可以看到,在定義 IMyCache 介面時,雖然預期 GetDataAsync 是個非同步方法
(注意方法名稱有 Async 後綴),但實作時仍有可能採取同步的方式——
當你看到這樣的寫法時,應特別留意是否無意間在非同步呼叫的流程中混雜了
同步/阻斷式呼叫,例如底下這個錯誤示範:


Public Task<string>GetDataAsync()
{
 String s =System.IO.File.ReadAllText(@"C:\temp\big.txt");
 // 錯誤示範!
 returnTask.FromResult(s);

}

範例

private void button3_Click(object sender, EventArgs e) { MessageBox.Show("Hello World"); MyVoidAsyncMethod().Wait(); MyVoidAsyncMethod2().Wait(); MyVoidAsyncMethod3(); //no await/async } public static async Task MyVoidAsyncMethod() { //We can await only methods which return Task or Task<T>
so this can be done by returning value from awaited completed task: //requires .net 4.6 await Task.CompletedTask; MessageBox.Show("After Task.CompletedTask"); } //Task.FromResult(0) public static async Task MyVoidAsyncMethod2() { //We can await only methods which return Task or Task<T> so this can be done by returning value from awaited completed task:
//requires .net 4.5 await Task.FromResult(0); MessageBox.Show("After Task.FromResult(0)"); } public static Task MyVoidAsyncMethod3() { MessageBox.Show("no async/await Task.FromResult(0)"); return Task.FromResult(0); }


沒有留言:

張貼留言