Unity에서 Coroutine은 단일 프레임 내에서 작업을 수행하지 않고 다수 프레임에서 작업을 수행할 수 있다.
그로 인해 비동기적 처리 방식을 위한 코드를 구현할 때는 Coroutine을 많이 사용하기도 한다.
하지만 Coroutine의 이점만 바라보고 사용하기에는 불이점도 분명히 존재하기 때문에, Coroutine에 대해 더 자세히 알아보고 정리하자는 마음으로 이 글을 작성하게 되었다.
Coroutine은 비동기처리 방식?
비동기에 대한 이해가 부족하다면 아래의 글을 보고오자.
Synchronous vs Asynchronous & Blocking vs Non-blocking
Synchronous와 Asynchronous, Blocking과 Non-blocking이라는 말은 운영체제(OS)를 공부하면서 듣게 된 개념이다.프로그래밍을 함에 있어 중요한 개념인 만큼 이번 기회에 정리를 해보고자 한다.Synchronous와 Asyn
lms0408.tistory.com
Unity Documentation에 코루틴에 대한 설명을 보면 다음과 같다.
※ 코루틴은 HTTP 전송, 에셋 로드, 파일 I/O 완료 등을 기다리는 것과 같이 긴 비동기 작업을 처리해야 하는 경우 코루틴을 사용하는 것이 가장 좋습니다. |
- https://docs.unity3d.com/kr/2022.3/Manual/Coroutines.html
코루틴 - Unity 매뉴얼
코루틴을 사용하면 작업을 다수의 프레임에 분산할 수 있습니다. Unity에서 코루틴은 실행을 일시 정지하고 제어를 Unity에 반환하지만 중단한 부분에서 다음 프레임을 계속할 수 있는 메서드입니
docs.unity3d.com
유니티의 Coroutine은 작업을 다수의 프레임에 분산하여 수행할 수 있는 Class이다.
즉, 작업이 완료되지 않더라도 다른 작업을 수행할 수 있다는 뜻이다.
그렇기 때문에 유니티의 Coroutine은 비동기 방식이다.
Coroutine 동작 원리
Unity의 Coroutine은 반환 키워드 yield와 Coroutine의 반환 타입인 IEnumerator를 통해 비동기 작업을 수행한다.
'yield'란 양도하다는 의미로 Unity에서 Coroutine을 사용할 때의 yield 키워드는 함수의 실행을 일시 중지하고, 호출자에게 제어를 반환한다는 의미라고 볼 수 있다.
또한 yield 키워드 뒤에 오는 표현식에 따라 Coroutine이 재개될 조건을 지정할 수 있다.
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
IEnumerator(열거자)는 컬렉션을 단순하게 반복할 수 있도록 지원해주며, System.Collections 네임스페이스에 속한 interface이다.
IEnumerator는 현재 위치의 데이터를 반환해주는 Object Property와 다음 데이터로이동 가능에 대한 반환과 이동을 시켜주는 Bool형 메서드, 인덱스를 초기 상태 위치로 변경해주는 Void형 메서드로 이루어져있다.
다수 프레임에서 작업을 나누어 수행할 수 있는 이유는
yield return 문이 컴파일시 코드의 위치를 저장하는 상태 머신으로 변환되어
yield를 기준으로 작업을 나누어 수행할 수 있기 때문이다.
코루틴을 실행하는 메서드는 기본적으로 Enumerator에 함수의 포인터 값으로 저장된다.코루틴 실행 시 반복문을 만나면서 MoveNext()가 호출된다.이때 IEnumerator 반환형 메서드가 yield문을 만나기 전 까지 실행이 된다.
yield문을 만나게 되면 Current인 YieldInstruction(waitForSeconds와 같은)이 실행되게 된다.이때 리턴되는 값이 존재한다면 MoveNext()의 결과값은 true로 반환되고,없을 시 false를 반환하여 반복문이 종료된다.
MoveNext()의 반환 값이 true가 되어 Current를 실행하였다면,다시 MoveNext()가 호출되면서 yield return이 일어났던 위치의 다음줄 부터 재실행이 된다.이때 역시 마찬가지로 yield 문을 만나기 이전까지 진행된다.
위의 과정을 반복하여 최종적으로 MoveNext()의 반환 값이 false가 반환되면 반복문이 종료되면서 코루틴 실행이 끝이 나게 된다.
Coroutine 사용법
Coroutine을 사용하기 위해서는 첫 번째로 IEnumerator 반환형으로 메서드를 정의해주어야 한다.
이때 반환형은 한 개 이상의 yield 키워드를 포함한 return 문이 작성되어야 한다.
IEnumerator CoroutineTest()
{
// ...Something
yield return ...
}
이렇게 정의된 메서드는 MonoBehaviour에 상속된 인스턴스에서 StartCoroutine() 메서드를 통해 실행시킬 수 있다.
public class CoroutineControl : MonoBehaviour
{
// ... CoroutineTest Method
void Start()
{
StartCoroutine(CoroutineTest());
}
}
StartCoroutine을 통해 생성된 Coroutine을 field 변수에 저장하여 Coroutine을 관리할 수 있다.
public class CoroutineControl : MonoBehaviour
{
// ... CoroutineTest Method
Coroutine coroutine;
void Start()
{
coroutine = StartCoroutine(CoroutineTest());
if ( // ... Any Condition )
{
StopCoroutine(coroutine);
}
}
}
이렇게 정지된 Coroutine은 어떠한 곳에도 참조가 되어있지 않을 시 GC(Garbage Collector)에 의해 수집되게 된다.
yield
Coroutine을 사용할 때 대표적으로 쓰는 반환형들은 다음과 같다.
yield return null | 다음 프레임까지 대기 |
yield return new WaitForSeconds() | 입력한 초(sec)만큼 대기 |
yield return new WaitFixedUpdate() | 다음 프레임의 FixedUpdate까지 대기 |
yield return new WaitForEndOfFrame() | 모든 랜더링 작업이 끝날 때까지 대기 |
yield return new StartCoroutine() | 입력한 다른 코루틴이 끝날 때까지 대기 |
yield return new WWW() | 입력한 웹 통신 작업이 끝날 때까지 대기 |
yield return new AsyncIoeration | 비동기 작업이 끝날때까지 대기 |
yield break | 코루틴 종료 |
Coroutine의 문제점
Coroutine은 비동기적 방식과 유사한 방식으로 작업을 편리하게 수행할 수 있는 만큼 남용할 시 문제가 생길 수 있다.
다음 예는 게임 내에서 Monster의 공격이 Coroutine으로 구현되어있는 상황에서 메모리를 낭비하는 모습이다.
public class Monster : MonoBehaviour
{
public IEnumurator DashAttack()
{
float waitAttackTime = 1f;
yield return new WaitForSeconds(waitAttackTime);
// ...Attack
yield break;
}
}
Monster의 수가 비약적으로 적은 경우에는 위의 코드를 사용했을 때 큰 문제는 발생하지 않을 것이다. (다만 Coroutine을 생성하여 공격한다는 점에서 메모리 손실이 발생한다.)
하지만 Monster의 수가 많아진다면 최대 n개(n개의 몬스터가 동시에 공격했을 때) 만큼 Coroutine의 인스턴스가 생성될 것이다.
더 최악의 상황은 이렇게 생성된 Coroutine이 재사용 가능성이 없게 된다면 사용한 Coroutine n개가 GC에 의해 수집이 되어 더 잦은 GC 호출을 초래할 수 있는 문제가 생긴다.
이렇게 잦은 GC 호출은 높은 CPU점유율로 인한 프레임 드랍 현상까지 이어지게 된다.
'Unity' 카테고리의 다른 글
유니티 프로젝트 개발 일지 - 1 (상태 패턴) (1) | 2024.12.18 |
---|---|
유니티 프로젝트 개발 일지 - 0 (0) | 2024.12.17 |
[Unity] Asset Management(에셋 관리) - Resources, AssetBundle, Addressable (4) | 2024.09.23 |
[Unity] 유니티 생명 주기(Unity Life Cycle) (3) | 2024.09.06 |
[Unity] 유니티 메모리 관리와 가비지 컬렉터(Garbage Collector) (2) | 2024.07.13 |