在開發 ASP.NET Core Web API 時在許多資料上都有看到應該要使用非同步而不是同步,並且非同步速度比較快,但是只有說明很少有提到到底快在哪裡?快多少?
以簡單的方式描述兩者的差異,在使用同步(sync)任務時,會有一個執行緒(thread)依照步驟順序依序執行程式碼任務到結束,但是在使用非同步(async)遇到 await 關鍵字來執行非同步任務時,在等待任務執行的過程中執行緒會被釋放,可以先去處理其他任務,等到原先的非同步任務完成後再回來繼續執行 await 之後的部分。
但是同步和非同步到底有沒有差別?差多少?我們建立兩個簡單的 API ,都是等待 100 毫秒後回應結果,程式碼如下:
然後在 Azure 上使用 App Service 服務建立了一個 B2 等級的服務(2 核心 3.5 GB 記憶體),使用 Apache JMeter 工具測試 API 的併發呼叫,以下是 50 個使用者各呼叫 10 次(每個使用者取得回應後就會直接再呼叫一次)的測試資料:
同步:
非同步:
可以發現在 50 個使用者各自連續呼叫 10 次共 500 次的呼叫中平均回應時間差了將近 5.5 倍,更詳細的資料如下:
可以發現大約在 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
以簡單的方式描述兩者的差異,在使用同步(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 |
筆者在測試的過程中使用了許多種方式,例如一直計算直到達到時間或是使用寫入資料直到指定時間,不過發現會遇到 CPU 和硬碟的效能瓶頸導致差異不明顯,後來才使用最單純的 Task.Delay ,也為了避免不同核心和執行緒的差異才放到 Azure 中做測試。在測試的過程中也是每個各呼叫 3 次,取中間值紀錄。
後來想一想才發現可能只要做 ? 個使用者同時發送一次就好,而不是像上面一樣發送 10 次。
參考資料:
Microsoft.Learn - Asynchronous programming with async and await
Microsoft.Learn - Asynchronous programming scenarios
感謝分享~
回覆刪除