Async/Await in .NET는 제한 없이 사용하면 리소스를 낭비할 수 있어요 🚀🛑
Source: Dev.to
소개
우리 중 많은 사람들은 async/await를 사용하면 애플리케이션이 자동으로 “스케일러블”해진다고 생각합니다. 하지만 제한(throttling) 메커니즘 없이 async 코드를 남발하면, 오히려 서버 자원을 순식간에 소모하는 무기고가 될 수 있습니다. 이 글(및 최신 YouTube 영상)에서는 Task.WhenAll을 제한 없이 사용할 때 왜 위험한지, 그리고 SemaphoreSlim이 어떻게 서버를 구해줄 수 있는지 살펴보겠습니다.
화면 뒤에서 무슨 일이 일어나나요?
- Socket Exhaustion – 서버가 외부 연결을 위한 포트를 모두 소진합니다.
- Database Pool Starvation – 모든 DB 연결이 사용 중이라 다른 요청이 대기합니다.
- RAM Spike – 각 작업마다 자체 상태 머신과 메모리 할당이 발생합니다.
그 결과 서버가 “멈추고”, 요청이 타임아웃되며, 사용자는 실망하게 됩니다.
SemaphoreSlim
SemaphoreSlim은 .NET에서 동시 실행 가능한 작업 수를 제한하는 가장 가벼운 방법입니다.
올바른 구현 방법
// 최대 10개의 동시 작업을 제한
using var semaphore = new SemaphoreSlim(10);
var tasks = dataList.Select(async d =>
{
await semaphore.WaitAsync(); // 여기서 대기열에 들어감
try
{
await CallExternalApiAsync(d);
}
finally
{
semaphore.Release(); // 다음 작업을 위해 슬롯 해제
}
});
await Task.WhenAll(tasks);
이 코드를 사용하면 1,000개의 데이터가 있더라도 동시에 처리되는 데이터는 10개에 불과합니다. 서버 자원은 안전하게 유지되고, 애플리케이션은 여전히 응답성을 유지합니다.
모범 사례
try...finally사용:Release()는 반드시finally블록에서 호출하세요. 그렇지 않으면 세마포 슬롯이 “누수”되어 애플리케이션이 데드락에 빠질 수 있습니다.- 합리적인 제한값 설정: 무작정 숫자를 정하지 말고, 자원(예: 데이터베이스 풀의 최대 연결 수)에 맞게 제한값을 조정하세요.
- Semaphore vs Lock: async 코드에서는 일반
lock사용을 피하세요.lock은 스레드를 차단하지만,WaitAsync()는 대기 중에도 스레드가 다른 작업을 수행하도록 해줍니다.
결론
async/await는 강력한 도구이지만, SemaphoreSlim은 그 강력함을 제어할 수 있게 해줍니다. 동시에 너무 많은 작업을 시도해서 서버가 무너지지 않도록 주의하세요. 적절히 동시성을 제한하면 애플리케이션은 여전히 빠르고 안정적으로 동작합니다.