동기 코드는 “한 줄이 끝나야 다음 줄로 넘어가는 방식”이다.
Console.WriteLine("1. 시작");
DoWork(); // 여기서 이 일이 끝날 때까지 멈춰 있음
Console.WriteLine("2. 끝");
DoWork() 안에서 5초가 걸리면,"2. 끝" 출력)은 절대 먼저 실행되지 않는다.UI 프로그램(WinForms, WPF, 웹 등)에서는 이렇게 오래 걸리는 작업을 동기로 하면, 화면이 멈춘 것처럼 느껴진다.
편의점 사장 입장에서 생각해 보자.
여기서 비유를 맞춰 보면:
Taskawaitasync 메서드각각을 조금 더 자세히 보자.
Task = 아직 안 끝났을 수도 있지만 언젠가는 끝날 ‘작업’ 하나를 나타내는 객체
두 가지 형태가 있다.
Task – 리턴값 없는 작업Task<T> – 나중에 T 타입 결과를 돌려주는 작업Task t = WorkAsync(); // 나중에 끝나는 작업 (리턴값 없음)
Task<int> t2 = CalcAsync(); // 나중에 int 결과를 하나 돌려줌
감각적으로는 “배달 주문 번호”랑 비슷하다.
주문 넣으면 바로 음식이 나오는 게 아니라 주문 번호(Task)를 받고, 음식은 나중에 나온다.
비동기 메서드를 만들 때는 이렇게 쓴다.
async Task DoSomethingAsync() { ... } // 리턴값 없음
async Task<int> GetNumberAsync() { ... } // int 리턴
async는 “이 안에서 await를 쓸 거야”라고 표시하는 키워드라고 보면 이해하기 쉽다.
실제 비동기 동작은 await가 담당한다.
await Task.Delay(2000);
이 한 줄의 의미는:
“2초 뒤에 끝나는 작업을 하나 시작해 놓고,
그 작업이 끝나면 현재 메서드의 다음 줄부터 다시 실행해 줘.”
중요한 점은, 이 기다리는 동안 스레드를 붙잡고 자는 게 아니라는 것.
“2초 뒤에 여기서 다시 이어서 실행해”라고 예약해 놓고, 스레드는 다른 일을 할 수 있게 돌려준다.
일단 콘솔 기준으로 제일 짧은 예제를 보자.
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("1. 시작");
await Task.Delay(2000); // 2초 동안 비동기 지연
Console.WriteLine("2. 2초 후에 실행");
}
}
"1. 시작"이 바로 출력된다.await Task.Delay(2000);를 만난다.
Console.WriteLine("2. 2초 후에 실행");이 실행된다.
겉으로 보기에는 Thread.Sleep(2000) 과 비슷하게 “2초 후에 다음 줄 실행”처럼 보이지만,
내부는 스레드를 멈추지 않고 비동기로 동작한다는 점이 다르다.
using System;
using System.Threading;
class Program
{
static void Main()
{
Console.WriteLine("A");
Thread.Sleep(2000); // 2초 동안 스레드가 진짜로 멈춰 있음
Console.WriteLine("B");
}
}
A → (2초 멈춤) → Busing System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("A");
await Task.Delay(2000); // 2초 동안 비동기 지연
Console.WriteLine("B");
}
}
출력 결과는 똑같이 A → 2초 후 → B 이지만, 내부 동작이 다르다.
Thread.Sleep : 스레드가 2초 동안 완전히 쉰다.await Task.Delay :
콘솔 앱에서는 체감이 안 될 수 있지만, UI 프로그램(WinForms, WPF, MAUI 등)에서는 이 차이가 “화면이 멈추느냐, 멈추지 않느냐”로 크게 드러난다.
아까 편의점 비유를 실제 코드로 옮겨 보면 훨씬 이해가 쉽다.
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("1. 라면 주문 넣기");
string result = await CookRamenAsync(); // 라면 끓이는 비동기 메서드 호출
Console.WriteLine("3. 라면 받음: " + result);
}
// 라면 끓이는 비동기 메서드
static async Task<string> CookRamenAsync()
{
Console.WriteLine("2. 라면 끓이는 중... (3초)");
await Task.Delay(3000); // 3초 동안 끓이는 중이라고 가정
return "완성된 라면";
}
}
CookRamenAsync의 선언:
static async Task<string> CookRamenAsync()
async → 이 메서드는 비동기 스타일로 동작한다.Task<string> → “나중에 string 결과를 줄 작업”을 나타낸다.<li><code>await CookRamenAsync()</code>의 의미:
<ul>
<li><code>CookRamenAsync()</code>를 호출하면 <code>Task<string></code>이 하나 생긴다.</li>
<li><code>await</code>는 그 작업이 끝날 때까지 기다렸다가,</li>
<li>끝나면 그 결과(<code>string</code>)를 꺼내서 <code>result</code>에 넣어준다.</li>
</ul>
</li>
정리하면:
async Task<T> 메서드 → “나중에 T를 줄게”라고 약속하는 비동기 메서드await → “그 약속(Task<T>)이 끝날 때까지 기다렸다가, T를 꺼내서 계속 진행해 줘”Task, Task<T> 두 가지가 핵심Task 또는 Task<T>로 만든다.async/await를 쓰면 동기 코드처럼 위에서 아래로 읽히는 구조로 만들 수 있어서 유지보수가 훨씬 쉽다.