Task는 .NET에서 비동기 처리와 병렬 처리를 위한 핵심 기능입니다.
코드의 흐름을 멈추지 않고 작업을 처리하거나, 여러 작업을 동시에 수행할 수 있게 도와줍니다.
Thread보다 가볍고, async/await과 함께 사용하면 코드도 간결해집니다.
Task 란?
Task는 작업 단위를 표현하는 클래스입니다.
.NET에서는 비동기, 병렬 처리를 가능하도록 도와줍니다.
개념이 조금 어렵긴한데요.
비동기와 병렬 처리를 하기위한 클래스 정도로 이해하는게 좋겠습니다.
비동기란?
병렬 처리는 아마 다들 알고 계실 것 같습니다.
비동기는 조금 생소할 수 있는데요.
비동기란 어떤 작업이 완료되기를 기다리는 동안 다른 일을 먼저 할 수 있는 구조 입니다.
이해하기 어렵지만,
예를 들면 카페에서 아메리카노를 내리면서 샌드위치를 만드는 것과 동일 합니다.
동기라면 아메리카노가 완성되고 샌드위치를 만듭니다.
정리하면 동기는 프로세스가 한줄씩 순차적으로 진행되는 반면 비동기는 순차적으로 진행되지 않습니다.
| 동기 방식 | 비동기 방식 | |
| 실행 순서 | 작업이 끝나야 다음으로 넘어감 | 중간에 멈춰도 다른 일 진행 가능 |
| 흐름 제어 | 순차적 | 이벤트 기반, 상태 저장 |
| 자원 효율성 | 낮음 (스레드 낭비) | 높음 (스레드 재활용 또는 미사용) |
Task 와 스레드(Thread) 관계
Task 와 스레드는 햄버거와 콜라 입니다.
스레드 또한 깊은 지식이 필요로하는 개념으로 따로 자세히 다뤄보도록 하겠습니다.
지금은 프로세스 안에서 코드를 읽어 나가는 "일꾼" 이라고 표현 하겠습니다.
Task는 직접 스레드를 생성하지는 않습니다.
.NET ThreadPool에서 스레드를 빌려다가 사용합니다.
사용한 스레드는 작업이 완료되면 ThreadPool에 다시 반납합니다.
ThreadPool은 인력사무소라 표현하겠습니다.
Task는 필요에 따라서 인력사무소에서 일꾼을 빌려다가 사용하고 다시 돌려줍니다.
동기 방식의 경우 스레드는 항상 1개 입니다.
Task 사용하면 1개 이상의 스레드를 가질 수 있습니다.
정리하면 Task 사용하면 여러명의 "일꾼"을 사용하여 프로세스를 좀 더 빠르게 처리할 수 있습니다.
Task 메모리 구조
Task 사용하면 메모리에 어떤 영향을 미칠까요.
Task는 힙(heap) 메모리에 생성되는 참조 타입 입니다.
메모리 안에 저장되는 상태는 아래와 같습니다.
- 실행 상태
- 반환값
- 예외 정보
- 콜백 등록 정보
- 취소 토큰
- 내부 동기화 객체
- 상태머신
핵심은 상태머신입니다.
async 키워드를 사용하면 컴파일러는 해당 코드를 상태머신 구조로 변환 시키고 각 await 마다 위치를 저장합니다.
상태머신은 힙 메모리에 저장되어 Task와 연결 됩니다.
async, await 관련해서는 밑에서 다루도록 하겠습니다.
지금은 비동기를 사용하기 위한 키워드로 기억해주세요.
Task가 완료되면 가비지컬렉터가 수집 가능한 상태가 됩니다.
async 와 await
async와 await는 비동기 프로그래밍을 쉽게 구현하기 위한 키워드 입니다.
이 둘은 Task 기반 비동기 작업을 동기적인 코드처럼 읽고 쓸 수 있도록 도와줍니다.
쉽게말해 비동기를 아주 간단하게 사용할 수 있게 도와주는 키워드라고 보시면 될 것 같습니다.
우선 async 키워드는 메서드 선언 앞에 붙으며 그 메서드가 비동기 메서드임을 선언 합니다.
async 반환 타입은 일반적으로 Task, Task<T>, void 3가지 사용 가능한데요.
주로 Task, Task<T> 반환형이 사용되고 void 잘 사용되지 않습니다.
async 선언해야 await 사용이 가능한데,
await는 키워드에서 알 수 있듯이 Task의 완료를 기다리는 키워드 입니다.
await는 Task가 완료될 때까지 기다리지만 현재 스레드를 차단하지 않고 비워두는 방식으로 동작합니다.
작업 스레드를 차단하지 않기 때문에 Task를 기다리는 동안 다른 작업이 CPU 자원을 사용할 수 있으므로 시스템 전체 성능이 좋아지고 응답성을 보장받을 수 있습니다.
await는 한마디로 "이 작업 끝나면 다음 줄부터 계속해" 라는 의미 입니다.
단 다른 작업들은 계속할 수 있도록 보장 해줍니다.
예제
이제 실제 코드 예제를 통해 비동기 및 병렬 처리에 대해서 알아보겠습니다.
아래 코드는 일반적인 동기 형식의 실행 프로세스 입니다.
정수 파라미터를 받고 2초 대기하는 메서드를 3번 호출하였습니다.
해당 프로세스는 예측하기가 쉽습니다.
정수 1을 파라미터로 전달한 코드부터 순차적으로 실행되기 때문입니다.
public class Program
{
public static void Main()
{
var st = Stopwatch.StartNew();
Default_Hello(1);
Default_Hello(2);
Default_Hello(3);
st.Stop();
Console.WriteLine(st.ElapsedMilliseconds);
}
public static void Default_Hello(int i)
{
Console.WriteLine($"{i}번째 Start");
Thread.Sleep(2000);
Console.WriteLine($"{i}번째 End");
}
}
아래 그림은 결과를 캡처한 화면 입니다.
위에서 예상한데로 1부터 3까지 순차적으로 프로세스가 진행되었습니다.
시간도 6초로 예상가능한 범위네요.

아래 코드는 기존 동기로 처리하던 부분을 비동기 및 병렬 처리로 변경한 코드 입니다.
Task 클래스를 사용하여 작업 단위를 변경하였고 async, await 사용해서 비동기를 적용하였습니다.
여러개의 Task 생성으로 병렬 처리도 적용된 것 입니다.
위에서 await는 Task 작업 완료까지 기다리는 키워드라고 말씀 드렸습니다.
같은 개념으로 Task.WhenAll은 모든 Task를 기다립니다.
아래 코드에서는 t1, t2, t3 3가지 Task를 기다린다고 볼 수 있습니다.
public class Program
{
public static async Task Main()
{
var st = Stopwatch.StartNew();
Task t1 = Default_Hello(1);
Task t2 = Default_Hello(2);
Task t3 = Default_Hello(3);
await Task.WhenAll(t1,t2, t3);
st.Stop();
Console.WriteLine(st.ElapsedMilliseconds);
}
public static async Task Default_Hello(int i)
{
Console.WriteLine($"{i}번째 Start");
await Task.Delay(2000);
Console.WriteLine($"{i}번째 End");
}
}
아래 화면은 실행 결과입니다.
1부터 3까지 시작을 동시에 했죠? 이게 Task의 특징 입니다.
하나의 스레드로 처리하는 방식에서 3개의 스레드가 동시에 처리하는 방식으로 변경된 것 입니다.
또 하나의 특징은 순서를 보장하지 않습니다.
End를 보시면 3번이 먼저 프로세스가 마무리 되었습니다.
동시에 시작하기에 어떤 프로세스가 먼저 끝나는지는 보장하지 않는 것 입니다.
그래서 await, Task.WhenAll 같은 대기 코드가 매우매우 중요 합니다.
마지막으로 소요 시간을 보시면 2초로 3배 빠른 성능을 볼 수 있습니다.

마치며..
일반적인 프로세스는 하나의 스레드를 가지며 스레드는 코드를 순차적으로 처리하는 역할을 한다.
Task는 스레드 풀에서 스레드를 빌려와 비동기 및 병렬 처리를 손쉽게 도와주는 클래스 이다.
async 사용하면 비동기이고 여러개의 Task 사용하면 병렬 처리 이다.
비동기 처리시 await, Task.WhenAll 같은 대기 코드를 통해 데이터를 보장 받아야 한다.
비동기 및 병렬 처리시 여러개의 스레드로 인해서 디버깅이 어렵다.
동기식 디버깅과 비교했을 때 스레드 단위로 진행되기 때문에 디버깅 포인트가 여기갔다 저기갔다 한다.
메모리!
'.NET' 카테고리의 다른 글
| .NET 델리게이트(Delegate)란 무엇인가. (0) | 2025.04.15 |
|---|---|
| .NET 람다식(Lambda)란 무엇인가. (0) | 2025.04.10 |
| .NET 데퍼(Dapper)란 무엇인가. (0) | 2025.04.08 |
| .NET 린큐(LINQ) 무엇인가. (0) | 2025.04.07 |
| .NET 의존성 주입(Dependency Injection) 무엇인가. (0) | 2025.03.28 |
