TOOLS GAMES DEVLOG DOCS ABOUT CONTACT
HOME > DOCS > Unity / C# > Coroutine vs C# async/await & Unity 6 Awaitable 비동기 프로그래밍 가이드
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 / UniTaskUnity 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 패턴 구현

ModernAsyncSequence.cs
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(...) 형태로 변수를 보관해야 합니다.

참고자료 & 관련 링크