C#

C# 기초부터 고급까지 Chapter 2.3. async/await 완벽 이해 – Task, ValueTask, await 동작원리까지

Juan_ 2025. 4. 29. 21:22
728x90

📚 Chapter 2.3: async/await 완벽 이해 – Task, ValueTask, await 동작원리까지


✅ 이 챕터에서 제대로 배울 것

  • 비동기(Asynchronous) 기본 개념
  • async/await 기본 문법
  • await 내부 동작 원리
  • Task와 ValueTask
  • async void 쓰면 왜 위험한지
  • 실무 패턴 총정리

1️⃣ 비동기(Asynchronous)란?


비동기 = 일 시켜놓고 결과 기다리면서 다른 일 한다.

현실 비유로:

  • 분식집에서 떡볶이 주문
  • 사장님이 바로 떡볶이 만들면서 다른 손님 주문도 받음
  • 떡볶이 다 되면 "떡볶이 나왔습니다!" 알려줌

✅ C#에서도 비슷하다!

  • 무거운 작업을 시키고 (ex. 파일 다운로드)
  • 기다리는 동안 다른 코드를 실행할 수 있다

2️⃣ async/await 기본 구조


🔥 async 키워드

  • 메서드가 비동기로 동작할 거라고 컴파일러에게 알려줌
  • 반드시 Task, Task<T>, 또는 ValueTask를 리턴해야 함

🔥 await 키워드

  • 비동기 작업(Task)이 끝날 때까지 기다림
  • 쓰레드를 점유하지 않고, 호출자에게 제어를 넘긴다

3️⃣ async/await 기본 예제


public async Task<int> GetDataAsync()
{
    await Task.Delay(1000); // 1초 쉬기 (쓰레드 안 점유)
    return 42;
}

✅ 해석:

  • Task.Delay(1000) → 비동기 작업 시작
  • await → "이 작업 끝날 때까지 기다릴게! 하지만 나 쓰레드 막지 않는다!"
  • 작업 끝나면 42 리턴

4️⃣ await 정확한 동작 원리 (완전 디테일)


🧠 await는 내부에서 이렇게 동작한다!

  1. await를 만나면
  2. Task 객체를 확인한다.
  3. 만약 Task가 이미 끝났으면 (ex. 캐시된 결과)
    • 바로 다음 코드로 넘어간다.
  4. 만약 Task가 아직 안 끝났으면
    • 현재 메서드를 중단(Suspend) 시킨다.
    • 호출자에게 제어권(Return) 넘긴다.
    • 컴퓨터는 다른 일을 할 수 있다.
  5. Task가 완료되면
    • C# 런타임이 "너 Task 끝났어!" 하고 알려준다
    • 중단했던 메서드를 다시 이어서 실행(Resume) 한다.

✋ 정말 중요한 것!!

await는 쓰레드를 점유하거나 막지 않는다!

  • 기존 쓰레드는 다른 일 하러 가버린다
  • Task가 끝났을 때만 다시 이어붙인다

"await은 중단(suspend) + 재개(resume) 컨트롤러다!"


5️⃣ await 내부 상세 흐름 (그림)


[메서드 시작]
    ↓
[await 만나면?]
    ↓
(1) Task 완료?
        → YES : 바로 다음 코드로
        → NO  : 현재 메서드 중단, 호출자에게 제어권 반환
    ↓
[Task 완료 이벤트 발생]
    ↓
[중단된 메서드 다시 이어서 실행]

✅ 이 메커니즘 덕분에

  • 서버가 엄청나게 많은 비동기 요청을 동시에 처리할 수 있는 거다!

6️⃣ Task vs ValueTask


타입 설명 특징
Task 비동기 작업 결과 거의 대부분 이걸 씀
ValueTask Task를 쓰기엔 너무 가벼운 작업용 최적화용으로만 제한 사용

✅ 실무에서는 무조건 Task부터 써라.
(ValueTask는 성능 튜닝 최후의 카드임)


7️⃣ async void 쓰면 왜 안 되나?


✅ async void는 "잡을 수 없는 에러"를 만든다.

  • Task 기반 async 메서드는 try-catch 가능
  • 근데 async void는 리턴 타입이 없으니까
  • 호출자 입장에서 예외 핸들링 불가능

✅ 그래서 이벤트 핸들러(버튼 클릭) 말고는 절대 async void 쓰지 말자!

async Task가 표준이다!!


8️⃣ 실전 async/await 패턴


📚 비동기 API 호출 예시

public async Task<string> GetWeatherAsync()
{
    using var client = new HttpClient();
    var response = await client.GetStringAsync("https://api.weather.com/today");
    return response;
}

✅ await를 만나도
✅ 쓰레드는 다른 일 하러 가고
✅ 날씨 API 응답 끝나면 다시 이어서 실행된다


📚 무거운 작업을 비동기로 분리

public async Task ProcessBigDataAsync()
{
    var data = await LoadDataAsync();
    var result = await AnalyzeDataAsync(data);
    await SaveResultsAsync(result);
}

✅ 무거운 데이터 처리도
✅ 비동기로 이어붙이면
✅ 프로그램 응답성(Responsiveness) 유지 가능!


9️⃣ 주의사항: ConfigureAwait(false)


✅ 실무에서는 가끔

await SomeAsyncMethod().ConfigureAwait(false);

요렇게 쓴다.

의미:

  • "작업 끝난 후에 원래 SynchronizationContext (UI 스레드 등)로 돌아오지 말고"
  • "아무 스레드에서라도 그냥 이어서 실행해라!"

✅ 서버 사이드(.NET Web API)에서는 무조건 ConfigureAwait(false) 추천한다.
(불필요한 컨텍스트 스위칭을 막기 위해)


✅ 최종 요약


항목 요약
async 비동기 메서드 선언
await 비동기 작업 기다리되, 쓰레드는 안 막음
Task 비동기 작업 결과
ValueTask 최적화용 특수 Task
async void 이벤트 핸들러 외 금지
ConfigureAwait(false) 서버에서는 기본 옵션으로 쓰기

🎉 여기까지 오면:

async/await은 단순 암기가 아니라,

"내부 동작 구조"까지 완전히 꿰뚫은 상태다!!


📢 다음 스텝

다음 챕터 주제
Chapter 2.4 LINQ 실무 활용법 (쿼리 vs 메서드 문법)
728x90