不要寫「假的」非同步方法
Michael Tsai
1/11/2016
前文提過一個撰寫非同步程式的通用建議:從頭到尾都採用非同步呼叫。可是,有時候就是沒辦法做到這點。
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"); // 此寫法有點問題,稍後說明。 } }
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
關鍵字。接著要繼續討論這個問題。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"); // 錯誤示範! return Task.FromResult(s); }