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#에서 new로 생성한 객체는 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;

7. async / await

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

var user = await GetFromDB();

Console.WriteLine("2");

return user;
}

실행흐름

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
GetUser()

"1" 실행

GetFromDB() 호출

await

DB 작업이 아직 완료되지 않음

GetUser() 실행 보류

현재 Thread 반환

ThreadPool은 다른 작업 처리

DB 응답

Task 완료

await 이후 코드 재개

"2" 실행

return user
Prev
2026-09-14 15:09:05
Next