[英]ASP.NET Web API 2 Async action methods with Task.Run performance
我正在嘗試使用幾個ASP.NET Web API 2.0端點進行基准測試(使用Apache工作台)。 其中一個是同步和一個異步。
[Route("user/{userId}/feeds")]
[HttpGet]
public IEnumerable<NewsFeedItem> GetNewsFeedItemsForUser(string userId)
{
return _newsFeedService.GetNewsFeedItemsForUser(userId);
}
[Route("user/{userId}/feeds/async")]
[HttpGet]
public async Task<IEnumerable<NewsFeedItem>> GetNewsFeedItemsForUserAsync(string userId)
{
return await Task.Run(() => _newsFeedService.GetNewsFeedItemsForUser(userId));
}
在觀看史蒂夫桑德森的演講之后,我向每個端點發出了以下命令ab -n 100 -c 10 http://localhost....
我很驚訝,因為每個端點的基准似乎大致相同。
關閉史蒂夫解釋說我期待異步端點更高效,因為它會立即將線程池線程釋放回線程池,從而使它們可用於其他請求並提高吞吐量。 但數字看起來完全一樣。
我在這里誤解了什么?
使用await Task.Run
創建“異步” WebApi是一個壞主意 - 你仍然會使用一個線程,甚至來自用於請求的同一個線程池 。
這將導致一些不愉快的時刻在這里詳細描述:
- 額外(不必要的)線程切換到Task.Run線程池線程。 類似地,當該線程完成請求時,它必須輸入請求上下文(這不是實際的線程切換但確實有開銷)。
- 創建額外(不必要的)垃圾。 異步編程是一種權衡:以更高的內存使用量為代價,您可以提高響應速度。 在這種情況下,您最終會為完全不必要的異步操作創建更多垃圾。
- Task.Run“意外地”借用線程池線程拋出ASP.NET線程池啟發式。 我在這里沒有很多經驗,但我的直覺告訴我,如果意外任務真的很短,那么啟發式應該可以很好地恢復,並且如果意外任務持續超過兩秒,則不能優雅地處理它。
- ASP.NET無法提前終止請求,即,如果客戶端斷開連接或請求超時。 在同步的情況下,ASP.NET知道請求線程並可以中止它。 在異步情況下,ASP.NET不知道輔助線程池線程是“for”該請求。 可以通過使用取消令牌來解決此問題,但這不在本博文的范圍內。
基本上,您不允許任何與ASP.NET的異步 - 您只需隱藏異步外觀后面的CPU綁定同步代碼。 Async
本身是I / O綁定代碼的理想選擇,因為它允許以最高效率利用CPU(線程)(不阻塞I / O),但是當你有Compute綁定代碼時,你仍然必須利用它CPU程度相同。
考慮到Task
和上下文切換的額外開銷,您將獲得比使用簡單同步控制器方法更糟糕的結果。
如何使其成為真正的ASYNC:
GetNewsFeedItemsForUser
方法應該變成async
。
[Route("user/{userId}/feeds/async")]
[HttpGet]
public async Task<IEnumerable<NewsFeedItem>> GetNewsFeedItemsForUserAsync(string userId)
{
return await _newsFeedService.GetNewsFeedItemsForUser(userId);
}
去做吧:
async
變體(如果沒有 - 運氣不好,你將不得不搜索一些競爭模擬)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.