ASP.NET Core Web API sync 和 async 差距比較

在開發 ASP.NET Core Web API 時在許多資料上都有看到應該要使用非同步而不是同步,並且非同步速度比較快,但是只有說明很少有提到到底快在哪裡?快多少?

以簡單的方式描述兩者的差異,在使用同步(sync)任務時,會有一個執行緒(thread)依照步驟順序依序執行程式碼任務到結束,但是在使用非同步(async)遇到 await 關鍵字來執行非同步任務時,在等待任務執行的過程中執行緒會被釋放,可以先去處理其他任務,等到原先的非同步任務完成後再回來繼續執行 await 之後的部分。

但是同步和非同步到底有沒有差別?差多少?我們建立兩個簡單的 API ,都是等待 100 毫秒後回應結果,程式碼如下:
    
using Microsoft.AspNetCore.Mvc;


[ApiController]
[Route("[controller]")]
public class AsyncVsSyncTestController : ControllerBase
{
    [HttpGet("sync")]
    public IActionResult GetSync()
    {
        Task.Delay(100).Wait();
        return Ok("Sync Response");
    }

    [HttpGet("async")]
    public async Task<IActionResult> GetAsync()
    {
        await Task.Delay(100);
        return Ok("Async Response");
    }
}
    

然後在 Azure 上使用 App Service 服務建立了一個 B2 等級的服務(2 核心 3.5 GB 記憶體),使用 Apache JMeter 工具測試 API 的併發呼叫,以下是 50 個使用者各呼叫 10 次(每個使用者取得回應後就會直接再呼叫一次)的測試資料:
方法 總數 平均(毫秒) 最小(毫秒) 最大(毫秒) 偏差值 吞吐量
sync 500 731 118 2,639 547.89 66.1
async 500 134 117 457 32.91 296.7
同步:

非同步:

可以發現在 50 個使用者各自連續呼叫 10 次共 500 次的呼叫中平均回應時間差了將近 5.5 倍,更詳細的資料如下:
方法 測試數 總數 平均(毫秒) 最小(毫秒) 最大(毫秒) 偏差值 吞吐量
sync 1*10 10 131 122 199 22.51 7.6
async 1*10 10 129 119 183 18.12 7.7
sync 2*10 20 129 117 195 20.72 15.4
async 2*10 20 132 118 208 25.85 15.1
sync 5*10 50 128 117 215 20.52 38.2
async 5*10 50 128 118 192 18.92 38
sync 10*10 100 146 117 455 62.18 63.1
async 10*10 100 135 116 453 46.91 61.3
sync 25*10 250 288 115 959 239.55 77.3
async 25*10 250 131 114 442 29.1 163
sync 50*10 500 731 118 2,639 547.89 66.1
async 50*10 500 134 117 457 32.91 296.7
可以發現大約在 10 個使用者各自連續呼叫 10 次和更少請求的時候差別沒有那麼大,都在誤差範圍內。但是在之後差距就非常明顯了。結論就是如果 API 的同時使用人數很少,小於 10 個以下,並且每個步驟都不會有很久的延遲(大約 100 毫秒以內),那同步和非同步兩種撰寫方式是幾乎沒有什麼差別的,但是當個別任務一久(本次測試僅測試 100 毫秒),或是有高併發(高併發沒有一定標準,但通常指 1 秒至少有 100 或 1,000)的需求,就算只是查詢資料庫這樣的操作只使用 100 毫秒也必須要使用非同步(對於資料庫高併發通常會有特別處理和快取,這裡只是舉例),避免隨請求數量造成回應時間指數及的增加。

筆者在測試的過程中使用了許多種方式,例如一直計算直到達到時間或是使用寫入資料直到指定時間,不過發現會遇到 CPU 和硬碟的效能瓶頸導致差異不明顯,後來才使用最單純的 Task.Delay ,也為了避免不同核心和執行緒的差異才放到 Azure 中做測試。在測試的過程中也是每個各呼叫 3 次,取中間值紀錄。
後來想一想才發現可能只要做 ? 個使用者同時發送一次就好,而不是像上面一樣發送 10 次。

參考資料:
Microsoft.Learn - Asynchronous programming with async and await
Microsoft.Learn - Asynchronous programming scenarios

留言

張貼留言

如果有任何問題、建議、想說的話或文章題目推薦,都歡迎留言或來信: a@ruyut.com