Unity / C#
Intermediate
Unity 2022.3+ / Unity 6
Coroutine vs C# async/await & Unity 6 Awaitable 비동기 프로그래밍 가이드
IEnumerator 코루틴의 한계와 WaitForSeconds 캐싱, UniTask 및 Unity 6 신규 Awaitable API 비교, 씬 전환 시 누수를 방지하는 CancellationToken 패턴.
#Unity #C# #Coroutine #async-await #Awaitable #Unity6
핵심 개념전통적인
Coroutine은 작성하기 쉽지만 값 반환(return value)이 불가능하고 예외 처리(try-catch)가 곤란합니다. C# Task는 멀티스레드 스케줄링으로 인해 유니티 메인 스레드 객체 접근 시 크래시가 발생할 수 있습니다. Unity 6에서 도입된 Awaitable은 GC를 유발하지 않는 제로-알로케이션 구조로 메인 스레드 전환과 지연 처리를 현대적으로 해결합니다.1. 3가지 비동기 방식 비교표
| 비교 항목 | Coroutine (IEnumerator) | Task / UniTask | Unity 6 Awaitable |
|---|---|---|---|
| 반환값 전달 | 불가 (Action 콜백 필요) | 가능 (Task<T> / UniTask<T>) | 가능 (Awaitable<T>) |
| try-catch 예외 처리 | yield 내부에서 catch 불가 | 완벽 지원 | 완벽 지원 |
| GC 할당량 | 반복 yield 시 발생 | UniTask: 제로에 근접 / Task: 힙 할당 | struct 기반 완전 제로 |
| 오브젝트 파괴 시 동작 | 자동 정지 (오브젝트 비활성 시) | 토큰 취소 없으면 고아 태스크로 누수 | destroyCancellationToken 기본 지원 |
| 권장 사용처 | 단순 깜빡임 연출 | 웹 통신, 복잡한 비즈니스 로직 | Unity 6 최신 게임플레이 시퀀스 |
2. 최신 Unity 6 Awaitable 패턴 구현
using System;
using System.Threading;
using UnityEngine;
public class ModernAsyncSequence : MonoBehaviour
{
[SerializeField] private SpriteRenderer spriteRenderer;
private async void Start()
{
try
{
// destroyCancellationToken: 오브젝트가 파괴되면 자동으로 취소 예외 발생
await PlayDamageBlinkAsync(5, 0.1f, destroyCancellationToken);
Debug.Log("피격 깜빡임 연출 완료!");
}
catch (OperationCanceledException)
{
// 씬 전환이나 오브젝트 파괴 시 안전하게 탈출
Debug.Log("연출이 취소되었습니다.");
}
}
/// <summary>
/// Unity 6 Awaitable 기반 제로 알로케이션 깜빡임 연출
/// </summary>
public async Awaitable PlayDamageBlinkAsync(int blinkCount, float interval, CancellationToken ct)
{
for (int i = 0; i < blinkCount; i++)
{
spriteRenderer.color = Color.red;
// 지정 초 대기 (GC 할당 0)
await Awaitable.WaitForSecondsAsync(interval, cancellationToken: ct);
spriteRenderer.color = Color.white;
await Awaitable.WaitForSecondsAsync(interval, cancellationToken: ct);
}
// 백그라운드 스레드에서 무거운 연산 후 메인 스레드로 안전 복귀
await Awaitable.BackgroundThreadAsync();
// [백그라운드 스레드]: 대용량 배열 정렬이나 패킷 압축 해제 등 수행
int calculatedScore = CalculateHeavyData();
await Awaitable.MainThreadAsync();
// [메인 스레드 복귀]: 안전하게 유니티 오브젝트 업데이트
Debug.Log($"연산 완료 결과: {calculatedScore}");
}
private int CalculateHeavyData()
{
int sum = 0;
for (int i = 0; i < 1000000; i++) sum += i % 7;
return sum;
}
}
3. 레거시 Coroutine 최적화 필수 팁
new WaitForSeconds캐싱: 코루틴 루프 내에서yield return new WaitForSeconds(0.1f)를 반복하면 매 틱마다 24바이트의 가비지가 발생합니다. 필드 변수로private readonly WaitForSeconds _wait = new WaitForSeconds(0.1f);를 생성해 두고 재사용하십시오.- 코루틴 핸들 캐싱:
StopCoroutine(myCoroutine)을 안전하게 호출할 수 있도록 실행 시Coroutine _handle = StartCoroutine(...)형태로 변수를 보관해야 합니다.