C# 서버 개발자 전환기 1편
2026-09-14 15:09:05

C# 동작 방식과 async/await

1. C# 코드가 실행되는 전체 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
C# 소스 코드
↓
C# Compiler (Roslyn)
↓
IL (Intermediate Language)
↓
.NET Runtime / CLR
↓
JIT Compiler
↓
Native Code
↓
CPU 실행

IL(중간 코드)를 쓰는 이유

1
2
3
4
5
6
7
      C# 코드
↓
IL 코드
↙ ↓ ↘
Windows Linux macOS
↓ ↓ ↓
x64 기계어 ARM 기계어 ...

플랫폼 독립성 때문에 필요함

2. CLR

CLR(Common Language Runtime)은 .NET 프로그램이 실행되는 환경

  • 메모리 관리
  • GC
  • 예외 처리
  • Thread 관리
  • JIT 컴파일
  • 비동기 작업 지원
  • 타입 안정성

3. GC

C#에서 class로 생성한 객체는 일반적으로 Managed Heap에 할당된다

1
var user = new User();
1
2
3
4
5
6
7
8
9
10
Stack
┌──────────────┐
│ user │
└──────┬───────┘
│
▼
Managed Heap
┌──────────────┐
│ User 객체 │
└──────────────┘

더 이상 참조되지 않는 객체는 GC가 나중에 메모리를 회수한다.

4. Thread

Thread는 실제로 코드를 실행하는 단위다.

1
2
3
Thread
↓
CPU에서 코드 실행

직접 Thread를 생성해야 하는 경우

1
2
var thread = new Thread(Work);
thread.Start();

5. ThreadPool

서버에서 요청마다 Thread를 새로 만들면 비효율적이기 때문에
.NET은 ThreadPool을 사용한다.

1
2
3
4
     ThreadPool
┌──────┬──────┬──────┐
│ T1 │ T2 │ T3 │ ...
└──────┴──────┴──────┘

6. Task

Task는 Thread가 아니고, 비동기 작업의 결과와 상태를 나타내는 객체다

1
2
3
4
5
6
7
8
9
10
11
12
User user = await GetUserAsync(); // 결과값이 필요한 경우
/*
비동기 작업 자체를 일단 가지고 있다가 나중에 결과를 받을 경우,
여기서 task는 User를 가지고 있는건 아님
User를 가져오는 비동기 작업을 나타내는 객체임
*/
Task<User> task = GetUserAsync();
Task<Item> itemTask = GetItemAsync();
Task<Rank> rankTask = GetRankAsync();
User user2 = await task; // 이제 결과가 필요해졌을 때
Item item = await itemTask;
Rank rank = await rankTask;

6.1 WhenAll()

여러 비동기 작업을 모두 기다릴 때 사용한다. JS Promise.all하고 같음

1
2
3
4
5
6
7
var userTask = GetUserFromDbAsync();
var rankingTask = GetRankingFromRedisAsync();

await Task.WhenAll(userTask, rankingTask);

var user = await userTask;
var ranking = await rankingTask;
1
2
3
4
DB 조회       ───────── 100ms
Redis 조회 ───────── 100ms
↓
총 100ms

7. async / await

1
2
3
4
5
6
7
8
9
10
public async Task<User> GetUserAsync()
{
Console.WriteLine("1");

var user = await GetFromDBAsync();

Console.WriteLine("2");

return user;
}

문법은 js async/await와 별 차이가 없다

7.1 실행 흐름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
GetUserAsync() 호출
↓
async 메서드 실행
↓
"1" 출력
↓
GetFromDBAsync() 호출
↓
Task<User> 반환
↓
await에서 Task 상태 확인
│
├─────────────── Task가 이미 완료됨 ───────────────┐
│ │
│ ↓
│ 바로 다음 코드 실행
│ │
│ ↓
│ "2" 출력
│ │
│ ↓
│ return
│
└─────────────── Task가 아직 실행 중 ──────────────┐
│
↓
State Machine에
현재 상태 저장
↓
continuation 등록
↓
GetUserAsync()가
Task를 반환
↓
Thread는 다른 작업 수행
↓
DB 작업 완료
↓
continuation 실행
↓
State Machine 상태 복원
↓
await 다음 코드 실행
↓
"2" 출력
↓
return
Prev
2026-09-14 15:09:05
Next