若是須要 I/O 綁定(例如從網絡請求數據或訪問數據庫),則須要利用異步編程。 還可使用 CPU 綁定代碼(例如執行成本高昂的計算),對編寫異步代碼而言,這是一個不錯的方案。web
異步編程的核心是 Task
和 Task<T>
對象,這兩個對象對異步操做建模。 它們受關鍵字 async
和 await
的支持。 在大多數狀況下模型十分簡單:數據庫
對於 I/O 綁定代碼,當你 await
一個操做,它將返回 async
方法中的一個 Task
或 Task<T>
。編程
對於 CPU 綁定代碼,當你 await
一個操做,它將在後臺線程經過 Task.Run
方法啓動。網絡
編寫代碼前應考慮的兩個問題:併發
1.你的代碼是否會「等待」某些內容,例如數據庫中的數據?app
若是答案爲「是」,則你的工做是 I/O 綁定。異步
2.你的代碼是否要執行開銷巨大的計算?async
若是答案爲「是」,則你的工做是 CPU 綁定。異步編程
若是你的工做爲 I/O 綁定,請使用 async
和 await
(而不使用 Task.Run
)。 不該使用任務並行庫。 相關緣由在深刻了解異步的文章中說明。spa
若是你的工做爲 CPU 綁定,而且你重視響應能力,請使用 async
和 await
,並在另外一個線程上使用 Task.Run
生成工做。 若是該工做同時適用於併發和並行,則應考慮使用任務並行庫。
你可能須要在按下按鈕時從 Web 服務下載某些數據,但不但願阻止 UI 線程。 只需執行以下操做便可輕鬆實現:
private readonly HttpClient _httpClient = new HttpClient(); downloadButton.Clicked += async (o, e) => { // This line will yield control to the UI as the request // from the web service is happening. // // The UI thread is now free to perform other work. var stringData = await _httpClient.GetStringAsync(URL); DoSomethingWithData(stringData); };
假設你正在編寫一個移動遊戲,在該遊戲中,按下某個按鈕將會對屏幕中的許多敵人形成傷害。 執行傷害計算的開銷可能極大,並且在 UI 線程中執行計算有可能使遊戲在計算執行過程當中暫停!
此問題的最佳解決方法是啓動一個後臺線程,它使用 Task.Run
執行工做,並 await
其結果。 這可確保在執行工做時 UI 能流暢運行。
private DamageResult CalculateDamageDone() { // Code omitted: // // Does an expensive calculation and returns // the result of that calculation. } calculateButton.Clicked += async (o, e) => { // This line will yield control to the UI while CalculateDamageDone() // performs its work. The UI thread is free to perform other work. var damageResult = await Task.Run(() => CalculateDamageDone()); DisplayDamage(damageResult); };
它無需手動管理後臺線程,而是經過非阻止性的方式來實現。
此示例演示如何爲一組 User
捕捉 userId
數據。
public async Task<User> GetUserAsync(int userId) { // Code omitted: // // Given a user Id {userId}, retrieves a User object corresponding // to the entry in the database with {userId} as its Id. } public static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds) { var getUserTasks = new List<Task<User>>(); foreach (int userId in userIds) { getUserTasks.Add(GetUserAsync(userId)); } return await Task.WhenAll(getUserTasks); }
如下是使用 LINQ 進行更簡潔編寫的另外一種方法:
public async Task<User> GetUserAsync(int userId) { // Code omitted: // // Given a user Id {userId}, retrieves a User object corresponding // to the entry in the database with {userId} as its Id. } public static async Task<User[]> GetUsersAsync(IEnumerable<int> userIds) { var getUserTasks = userIds.Select(id => GetUserAsync(id)); return await Task.WhenAll(getUserTasks); }
儘管它的代碼較少,但在混合 LINQ 和異步代碼時須要謹慎操做。 由於 LINQ 使用延遲的執行,所以異步調用將不會像在 foreach()
循環中那樣馬上發生,除非強制所生成的序列經過對 .ToList()
或 .ToArray()
的調用循環訪問。
LINQ 中的 Lambda 表達式使用延遲執行,這意味着代碼可能在你並不但願結束的時候中止執行。 若是編寫不正確,將阻塞任務引入其中時可能很容易致使死鎖。 此外,此類異步代碼嵌套可能會對推斷代碼的執行帶來更多困難。 Async 和 LINQ 的功能都十分強大,但在結合使用二者時應儘量當心。