콘텐츠로 이동

Unity 코루틴 동작 원리와 사용 시 주의점

코루틴은 멀티 스레드처럼 병렬(parallel)로 실행되지 않고, 협력적(cooperative)으로 번갈아 실행되는 루틴이다. 일반 함수는 호출되면 끝까지 실행되고 반환하지만, 코루틴은 yield로 실행을 멈추고 나중에 멈춘 지점부터 다시 이어 간다. 진입점이 여러 개인 함수라고 볼 수 있다.

Unity의 MonoBehaviour API는 메인 스레드에서만 호출할 수 있다. 그래서 여러 프레임에 걸쳐 진행되는 작업(연출, 대기, 비동기 요청 기다리기)을 스레드 없이 메인 스레드 안에서 나눠 처리할 때 코루틴을 쓴다.

Unity에서 코루틴은 IEnumerator를 반환하고 yield를 한 번 이상 쓰는 메서드다.

  1. 스크립트가 코루틴 메서드를 호출해 IEnumerator 객체를 만들고 StartCoroutine으로 엔진에 넘긴다.
  2. 엔진은 이 객체를 스케줄러에 보관하고, 그것을 가리키는 Coroutine 객체를 호출자에게 돌려준다.
  3. 엔진은 매 프레임 보관 중인 코루틴의 yield 조건이 끝났는지 확인하고, 끝났으면 MoveNext()를 호출해 다음 yield까지 실행한다.
  4. 메서드가 끝나거나, StopCoroutine이 호출되거나, 코루틴을 시작한 게임 오브젝트가 비활성화·파괴되면 코루틴이 정리된다.

IEnumerator 인터페이스는 다음과 같다.

public interface IEnumerator
{
object Current { get; } // 마지막으로 yield return한 값
bool MoveNext(); // 다음 yield까지 실행하고, 더 진행할 수 있으면 true
void Reset();
}

엔진 없이 직접 MoveNext()를 돌리면 yield 지점마다 멈췄다가 이어지는 흐름을 확인할 수 있다. WaitForSeconds도 엔진이 해석해야 의미가 있는 객체라서, 직접 돌리면 기다리지 않고 바로 다음 값으로 넘어간다.

IEnumerator HelloWorld()
{
yield return "hello";
yield return new WaitForSeconds(1.0f);
yield return "world!";
}
IEnumerator enumerator = HelloWorld(); // 이 시점에는 아무것도 실행되지 않는다
while (enumerator.MoveNext()) // 다음 yield까지 실행하고, 값은 Current에 담긴다
{
Debug.Log(enumerator.Current); // hello → WaitForSeconds 객체 → world!
}

스프라이트 알파 값을 줄이는 코드를 Update로 쓰면 매 프레임 조건을 검사한다.

public class Alpha : MonoBehaviour
{
private SpriteRenderer spriteRenderer;
void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
}
void Update() // 알파가 0이 된 뒤에도 매 프레임 호출된다
{
Color color = spriteRenderer.color;
if (color.a > 0.0f)
{
color.a -= 0.1f;
spriteRenderer.color = color;
}
}
}

코루틴으로 쓰면 필요한 동안만 실행되고, 간격도 직접 정할 수 있다.

public class Alpha : MonoBehaviour
{
private SpriteRenderer spriteRenderer;
void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
StartCoroutine(FadeOut());
}
IEnumerator FadeOut()
{
Color color = spriteRenderer.color;
while (color.a > 0.0f)
{
color.a -= 0.1f;
spriteRenderer.color = color;
yield return new WaitForSeconds(0.1f);
}
}
}
  • 다음 프레임까지 대기: null
  • 특정 시점까지 대기: WaitForSeconds, WaitForFixedUpdate, WaitForEndOfFrame
  • 다른 작업이 끝날 때까지 대기: 다른 코루틴, AsyncOperation, UnityWebRequest.SendWebRequest()

각 대기가 프레임의 어느 단계에서 풀리는지는 이벤트 함수의 실행 순서를 참고한다.

  • 시간이나 흐름을 의도적으로 지연시킬 수 있어 Update에서 매 프레임 상태를 검사하지 않아도 된다.
  • 인자를 넘길 수 없는 Invoke와 달리 매개변수를 받을 수 있다.
  • 메인 스레드에서만 실행되므로 락이나 컨텍스트 스위칭을 신경 쓰지 않아도 된다.
  • 메인 스레드에서 번갈아 실행될 뿐이라 무거운 연산을 나눠도 전체 처리 시간은 줄지 않는다. CPU를 많이 쓰는 작업은 스레드나 Job System을 검토한다.
  • 코루틴을 시작한 게임 오브젝트가 비활성화되면 코루틴도 멈추고, 다시 활성화해도 이어지지 않는다. 다시 시작해야 한다.
  • 이터레이터 메서드라서 ref·out 매개변수를 받을 수 없다.
  • 한 번 MoveNext()가 호출되면 다음 yield까지는 중간에 끊을 수 없다. StopCoroutine도 멈춰 있는 지점에서만 효과가 있다.
  • yield return은 catch나 finally 블록 안에 쓸 수 없고, catch가 있는 try 블록 안에도 쓸 수 없다. 익명 메서드나 람다 안에도 쓸 수 없다.
  • StartCoroutine을 호출할 때마다 객체가 생기고, new WaitForSeconds(...)도 매번 할당된다. 자주 도는 코루틴은 같은 대기 객체를 재사용하고, 필요 없는 코루틴은 멈춰 정리한다.