Coroutine vs async,await
Coroutine 과 async/await는 모두 유니티에서 사용하는 비동기 프로그래밍을 위한 구조입니다.
Coroutine 과 async,await의 공통점
1.
비동기적 실행 : 코루틴과 async/await 모두 코드의 실행을 일시 중지하고 다른 작업이 처리될 수 있도록 하여, 비동기적인 작업 흐름을 가능하게 합니다.
2.
메인 스레드 차단 방지 : 두 방법 모두 메인 스레드를 차단하지 않고 긴 작업을 처리할 수 있게 해주어, 애플리케이션의 반응성을 유지하는 데 도움을 줍니다.
Coroutine 과 async,await의 차이점
1.
구현 메커니즘
•
코루틴: 유니티의 코루틴은 IEnumerator 인터페이스를 사용하여 구현되며, yield return을 통해 실행을 일시 중지하고 관리합니다. 코루틴은 유니티 엔진의 프레임 업데이트 주기에 의존적입니다.
•
async/await: C#의 기본 언어 기능으로, Task 기반의 비동기 프로그래밍을 제공합니다. await 키워드는 Task의 완료를 기다리며, 작업이 완료되면 실행을 재개합니다.
2.
적용 범위
•
코루틴: 주로 유니티의 게임 개발에 특화되어 있으며, 시간 지연, 순차적 이벤트, 애니메이션 제어 등에 주로 사용됩니다.
•
async/await: 네트워킹, 데이터베이스 접근, 파일 I/O 등 보다 광범위한 프로그래밍 상황에 적합합니다.
3.
스레드 관리
•
코루틴 : 유니티의 메인 스레드에서 실행되며, 별도의 스레드 관리가 필요하지 않습니다.
•
async/await: 백그라운드 스레드에서 작업을 실행할 수 있으며, 복잡한 스레드 관리가 필요할 수 있습니다.
◦
다만, 별도의 처리를 하지 않은 작업들에선 이 또한 메인 스레드에서 실행됩니다.
◦
코드 예시
async void Start()
{
// 백그라운드 스레드에서 비동기 작업 실행
await Task.Run(() => LongRunningOperation());
}
// 오래 걸리는 작업을 모방하는 메서드
private async Task LongRunningOperation()
{
//..
}
C#
복사
4.
유니티와의 통합
•
코루틴: 유니티 엔진과 긴밀하게 통합되어 있어, 유니티의 API와의 상호작용이 자연스럽습니다.
•
async/await: 유니티에서 사용 가능하지만, 유니티 엔진의 특정 기능과의 상호작용 시 주의가 필요합니다.
Coroutine만이 할 수 있는 일 과 async,await Task만이 할 수 있는 일
코루틴이 할 수 있지만 async/await Task가 할 수 없는 일
1.
프레임 기반 대기 : 코루틴은 yield return null을 사용하여 다음 프레임까지 대기하거나 yield return new WaitForSeconds(seconds)를 사용하여 특정 시간 동안 대기할 수 있습니다.
2.
유니티 특정 기능과의 통합 : 코루틴은 유니티의 애니메이션, 사운드, 물리 엔진과 같은 기능과 밀접하게 통합되어 있어, 이러한 기능들과 연동된 작업을 수행하기에 적합합니다. 예를 들어, 애니메이션이 끝날 때까지 기다리거나 특정 이벤트가 발생할 때까지 대기하는 것이 이에 해당합니다.
위의 두 가지 의미는 코루틴이 유니티와 관련된 부분에서 더욱 세밀하게 측정할 수 있다는 것을 나타냅니다. 이러한 기능들은 Task에서 발전된 UniTask에서 충분히 적용할 수 있습니다.
async/await Task가 할 수 있지만 코루틴이 할 수 없는 일
1.
백그라운드 스레드에서의 실행 : async/await를 사용한 Task는 백그라운드 스레드에서 실행될 수 있어, 리소스 집약적인 작업이나 긴 실행 시간이 필요한 작업(예: 네트워크 요청, 파일 I/O, 데이터베이스 작업 등)을 메인 스레드를 차단하지 않고 수행할 수 있습니다. 이는 게임의 반응성을 유지하면서도 복잡한 작업을 처리할 수 있게 해줍니다.
IEnumrator, IEnumrable
유니티에서 코루틴은 IEnumerator 반환값을 가진 함수로 선언해야 합니다.
여기서 IEnumerator, IEnumerable에 대해 이해하면, 코루틴이 어떻게 동작하는지 더 자세히 이해할 수 있습니다.
IEnumerator
•
IEnumerator는 컬렉션의 각 요소를 순차적으로 순회할 수 있게 해주는 인터페이스입니다.
•
코루틴에서 IEnumerator는 시간에 따른 실행을 제어하는 데 사용됩니다. 코루틴에서는 yield return 문을 사용하여 실행을 일시 중지하고, 다음 프레임 또는 지정된 시간이 지난 후에 실행을 재개할 수 있습니다.
IEnumerable
•
IEnumerable은 IEnumerator 객체를 반환하는 메서드인 GetEnumerator()를 포함하는 인터페이스입니다.
List, Array, Dictionary와 같은 배열 요소들도 IEnumrable을 상속받고 있습니다.
따라서 foreach문과 같은 반복문을 사용할 수 있습니다.
IEnumerator의 주요 멤버는 다음과 같습니다
MoveNext()
•
MoveNext() 메서드는 열거자를 컬렉션의 다음 요소로 이동시킵니다.
•
이 메서드는 컬렉션이 더 이상 요소를 가지고 있지 않을 때 false를 반환하며, 그렇지 않으면 true를 반환합니다.
•
일반적으로 foreach 루프나 while 루프 내에서 사용되어 컬렉션의 모든 요소를 순회합니다.
Current
•
Current 프로퍼티는 열거자의 현재 위치에 있는 컬렉션 요소를 반환합니다.
•
MoveNext() 메서드가 처음 호출된 후에 Current를 사용하여 현재 요소에 접근할 수 있습니다.
Reset()
•
Reset() 메서드는 열거자를 그 시작 위치, 즉 컬렉션의 첫 번째 요소 이전의 위치로 재설정합니다.
•
이 메서드는 모든 IEnumerator가 구현하지 않을 수 있으며, 구현되어 있지 않은 경우 NotSupportedException을 발생시킬 수 있습니다.
•
Reset()의 사용은 드물며, 대부분의 경우 열거자를 다시 사용하려면 새 열거자 인스턴스를 생성하는 것이 더 낫습니다.
List<int> numbers = new List<int> { 1, 2, 3 };
var enumerator = numbers.GetEnumerator();
while (enumerator.MoveNext()) {
var current = enumerator.Current;
}
IEnumerator enumerator = numbers.GetEnumerator(); // Reset을 사용하지 않고 초기화
C#
복사
WaitForCompletion
이 부분은 현재 목차와는 관련이 적지만, async와 await를 동기적으로 처리할 때 주의해야 할 점을 말씀드리고 싶습니다.
우선, async와 await이 사용된 부분을 동기적으로 처리하려면, WaitForCompletion을 사용하여 메인 스레드의 동작을 중지시켜 동기적으로 처리합니다.
var operation = Addressables.LoadAssetAsync<T>(key);
operation.WaitForCompletion();
C#
복사
그러나, 이 부분은 해당 작업이 메인 스레드에서 실행될 때 작동하는 방법입니다. 위에서 설명한 다른 스레드에서 실행되는 방법을 사용하면, 메인 스레드는 동작이 멈추지 않습니다.
WaitForCompletion은 작업이 실행된 스레드를 멈추는 동작입니다.
다시 말해, 백그라운드 스레드에서 실행된 Task에 WaitForCompletion을 적용하면, 해당 백그라운드 스레드는 멈추게 됩니다.