[TIL] 비동기 프로그래밍(C Sharp)
개요
그동안 Unity에서 비동기 메서드를 코루틴으로 구현했지만, UniTask 라이브러리를 통해 다른 방식으로 비동기 메서드를 작성해보고자 한다
스레드 기본 개념
스레드란?
- 프로그램 내에서 코드를 실행하는 독립적인 실행 흐름
- cpu가 동시에 여러 작업을 처리하는 것처럼 보이게 만드는 단위
Unity 엔진은 메인 스레드에서 게임 로직을 실행한다
Unity API와 스레드 제약
Unity의 거의 모든 API는 메인 스레드에서만 호출 가능
// ✅ 메인 스레드에서 실행 - 정상 동작
void Update()
{
transform.position = Vector3.zero;
GameObject obj = Instantiate(prefab);
Debug.Log("Hello");
}
// ❌ 다른 스레드에서 실행 - 예외 발생
void SomeBackgroundThread()
{
transform.position = Vector3.zero; // 예외!
GameObject obj = Instantiate(prefab); // 예외!
}
백그라운드 스레드
무거운 작업은 별도의 스레드에서 실행해야 메인 스레드가 멈추지 않는다
주의: 백그라운드 스레드에서 Unity API를 사용할 수 없다(Transform, Rigidbody 등)
C Sharp 비동기 프로그래밍
목표: 코루틴으로 처리하던 코드들을 C# 표준 비동기(async, await, Task)로 작성하기
async/await가 해결하는 문제
- 메인 스레드 블로킹 x
- 자동으로 메인 스레드로 복귀
- 복잡한 스레드 관리 없이 간단한 코드 작성
- Unity API를 안전하게 사용
코루틴과의 비교
| 구분 | 코루틴 | async/await |
|---|---|---|
| 실행 주체 | Unity 엔진 | .NET 런타임 |
| 반환 타입 | IEnumerator | Task, Task |
| 재개 시점 제어 | yield return | await |
| 예외 처리 | 어려움 | try-catch 사용 가능 |
| 취소 | StopCoroutine | CancellationToken |
코루틴 vs async/await
IEnumerator MyCoroutine()
{
transform.position = Vector3.zero; // ✅ 가능
yield return new WaitForSeconds(1);
transform.position = Vector3.one; // ✅ 가능 (항상 메인 스레드)
}
코루틴의 핵심: Unity가 매 프레임 메인 스레드에서 MoveNext() 호출 → 항상 Unity API 사용 가능
async/await도 비슷하게 동작
async Task MyAsyncMethod()
{
transform.position = Vector3.zero; // ✅ 가능
await Task.Delay(1000);
transform.position = Vector3.one; // ✅ 가능 (자동으로 메인 스레드 복귀)
}
Task.Run
: 백그라운드 스레드 풀에서 작업 실행
- CPU 집약적 계산에 적합
- Unity API 절대 사용 금지
async Task CorrectUsage()
{
// 1. 백그라운드에서 계산만
int result = await Task.Run(() => {
int sum = 0;
for (int i = 0; i < 1000000; i++)
sum += i;
return sum; // Unity API 안 씀 ✅
});
// 2. await 이후 자동으로 메인 스레드로 복귀
Debug.Log($"결과: {result}"); // ✅ 안전
transform.position = new Vector3(result, 0, 0); // ✅ 안전
}
Task란?
- 비동기 작업의 실행 상태를 나타내는 객체
- "미래에 완료될 작업"을 표현하는 약속
Task의 생명주기 (상태)
async 메서드가 반환하는 Task는 WaitingForActivation에서 시작한다:
- WaitingForActivation: 비동기 작업 진행 중 (async 메서드 Task의 시작 상태)
- Running: 작업 실행 중
- RanToCompletion: 성공적으로 완료
- Faulted: 예외 발생으로 실패
- Canceled: 취소됨
Created 상태는 new Task()로 직접 만든 cold task 전용이라, async/await 코드에서는 보이지 않는다.
async/await 패턴과 동작 원리
async 키워드
- 메서드를 비동기 메서드로 표시
- 메서드 내에서 await 사용 가능하게 함
- 반환 타입: Task, Task , void(이벤트 핸들러만), ValueTask, ValueTask
await 키워드
- Task가 완료될 때까지 비동기적으로 대기
- 메서드 실행을 일시 중단하고 호출자에게 제어 반환
- Task 완료 시 메서드 재개
실행 흐름 예시
async Task ExampleFlow()
{
Debug.Log("1. 시작");
await Task.Delay(1000); // 여기서 메서드 일시 중단
Debug.Log("2. 1초 후"); // Task 완료 후 재개
}
실제 실행 순서:
- “1. 시작” 출력
- Task.Delay(1000) 시작, 메서드는 즉시 반환 (호출자로 제어 이동)
- 1초 후 Task 완료, 메서드 재개
- “2. 1초 후” 출력

