Triệu chứng "latency tăng, CPU thấp, request xếp hàng" gần như luôn là thread pool starvation: các thread của pool đang bị block để chờ I/O thay vì được trả lại pool.
Xác nhận bằng số liệu (dotnet-counters monitor --process-id <pid> System.Runtime):
- ThreadPool Thread Count tăng dần và ổn định ở mức cao — quá khoảng 3 lần số core là dấu hiệu rõ.
- ThreadPool Queue Length liên tục lớn hơn 0.
- Đúng lúc đó CPU vẫn thấp.
Nguyên nhân là pool chỉ bơm thêm thread rất chậm (mặc định khoảng 1–2 thread mỗi giây khi vượt min threads), nên khi thread bị giữ, hàng đợi dồn nhanh hơn tốc độ hồi phục.
Truy nguyên: dotnet-stack report hoặc dotnet-dump rồi xem call stack của các thread bị block. Thủ phạm quen thuộc:
- .Result / .Wait() / GetAwaiter().GetResult() bọc quanh code async (sync-over-async).
- lock giữ lâu, hoặc Task.Run bọc quanh việc I/O-bound (không tạo thêm năng lực, chỉ đẩy việc block sang thread khác).
- Đọc/ghi stream, file, HTTP bằng overload đồng bộ.
Khắc phục: async xuyên suốt từ controller xuống repository. ThreadPool.SetMinThreads chỉ là biện pháp giảm đau tạm thời để qua cơn cao điểm, không sửa được nguyên nhân.