이미 여러 연습 프로젝트에서 코루틴을 활용해왔다.
그런데 코루틴이란 것은 쓰면 쓸수록 이상한 녀석인 것 같다.
동기적인 방식으로 비동기적인 동작을 가능하게 한다는 점,
yield return 뒤에 어떤 값을 넣느냐에 따라 다양하게 작동하고 활용 또한 달라진다는 점
..등등 신기한 부분이 정말 많다.
이때까지 그런가보다 하면서 쓰다가 좀 더 구체적으로 설명해보려고 하니 내가 알고 있는 게 뭐지 싶다.
몰라도 어떻게든 쓸 수는 있겠지만 그래도.. 코더는 되지 말아야지 하는 생각에 코루틴을 뜯어보기로 했다.
그래서 오늘 코루틴을 공부하려고 했는데...!
무엇부터 공부해야 할지 감이 잡히지 않아서 제일 이상해 보이는 "yield return"이라는 녀석을 건드려 보기로 했다.
yield 문 - 반복기의 다음 요소 제공 - C#
반복기에서 yield 문을 사용하여 다음 값을 제공하거나 반복의 끝임을 알릴 수 있습니다.
learn.microsoft.com
var numbers = ProduceEvenNumbers(5);
Console.WriteLine("Caller: about to iterate.");
foreach (int i in numbers)
{
Console.WriteLine($"Caller: {i}");
}
IEnumerable<int> ProduceEvenNumbers(int upto)
{
Console.WriteLine("Iterator: start.");
for (int i = 0; i <= upto; i += 2)
{
Console.WriteLine($"Iterator: about to yield {i}");
yield return i;
Console.WriteLine($"Iterator: yielded {i}");
}
Console.WriteLine("Iterator: end.");
}
// Output:
// Caller: about to iterate.
// Iterator: start.
// Iterator: about to yield 0
// Caller: 0
// Iterator: yielded 0
// Iterator: about to yield 2
// Caller: 2
// Iterator: yielded 2
// Iterator: about to yield 4
// Caller: 4
// Iterator: yielded 4
// Iterator: end.
...읽는데.... 예문에서 Console에 찍히는 순서가 왜저러지....?
numbers에 값을 할당하는 과정에서 이미 메서드가 호출되었고 IEnumerable<int>가 이미 들어와 있으니 ProduceEvenNumbers 내부의 Console.WriteLine()들이 먼저 실행이 되어야 하는 것이 아닌가..!?
진짜 이상한 녀석은 yield return이 아닌 IEnumerable인 것 같아서 공부 방향을 돌렸다.
아무튼 오늘 TIL 주제는 '코루틴'...에 다가가기 위한
IEnumerable & IEnumerator
코루틴이란?
코루틴은 IEnumerator 타입을 반환하는 메서드로, yield return 문에서 특정한 값을 반환하면 메서드의 실행이 중단되어 지연 실행이 가능하게 해주는 것이다.
IEnumerable<T> & IEnumerator<T>
yield return을 만나면 호출자에게 값을 전달해주고 MoveNext()를 호출하도록 한다는 설명을 봤는데 이게 대체 무슨 소리인지 이해를 하기 위해 자료를 계속 찾아보았다.
IEnumerator란 무엇인가?
IEnumerator란 무엇인가? - Download as a PDF or view online for free
www.slideshare.net
↑↑ 가장 이해가 잘 되었던 설명
IEnumerable<T>
IEnumerable<T>를 반환하는 메서드를 호출하면, 해당 메서드의 실행이 즉시 시작되는 것이 아니라, 호출자에서 IEnumerator<T>를 통해 요소에 접근할 때마다 MoveNext가 호출되면서 메서드의 내용이 실행된다. 이는 지연 실행(Lazy Evaluation)이라고도 하는데, 호출자가 실제로 값을 필요로 할 때까지 연산을 미루는 것이기 때문에, 성능상 이점을 가져다 준다. 이렇게 하면 불필요한 계산이나 자원 소모를 피할 수 있다.
IEnumerator<T>
컬렉션의 요소에 대한 열거자(iterator)를 정의한다. 열거자는 현재 위치의 요소에 대한 정보를 가지고 있고, MoveNext 메서드를 호출하여 다음 요소로 이동한다. Current 속성을 통해 현재 위치의 요소에 접근할 수 있다.
코루틴 - Unity 매뉴얼
코루틴을 사용하면 작업을 다수의 프레임에 분산할 수 있습니다. Unity에서 코루틴은 실행을 일시 정지하고 제어를 Unity에 반환하지만 중단한 부분에서 다음 프레임을 계속할 수 있는 메서드입니
docs.unity3d.com
"C# 컴파일러는 코루틴을 지원하는 클래스 인스턴스를 자동으로 생성합니다."
구체적으로 살펴보면,
IEnumerable<T>를 반환하는 메서드에서 yield return을 사용하면, 컴파일러가 내부적으로 해당 메서드를 구현하는 클래스를 생성한다. 이 클래스는 IEnumerator<T> 인터페이스를 구현하며, 상태 기계의 동작을 제어한다. 이렇게 생성된 클래스의 인스턴스는 IEnumerable<T>를 구현하므로, 메서드의 호출 결과로써 IEnumerable<T>를 사용할 수 있게 된다.
예를 들어
IEnumerable<int> CountUpToFive()
{
for (int i = 1; i <= 5; i++)
{
yield return i;
}
}
라는 메서드가 있다고 하자. 이 메서드가 호출되면 C#에서는 아래와 같은 클래스를 만들어 객체를 반환한다.
(실제로 생성되는 클래스는 내부적으로 명명되며 사용자는 직접적으로 접근할 수 없다)
class CountUpToFiveStateMachine : IEnumerator<int>, IEnumerable<int>
{
private int state;
private int current;
public int Current => current;
public bool MoveNext()
{
switch (state)
{
case 0:
state = 1;
current = 1;
return true;
case 1:
state = 2;
current = 2;
return true;
// ... (이후 숫자 3, 4, 5에 대한 처리)
default:
state = 0;
current = 0;
return false;
}
}
// IEnumerator<T> 인터페이스의 나머지 멤버들도 구현되어 있음
public IEnumerator<int> GetEnumerator() => this;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
CountUpToFive 내부에 포함된 yield return문에 따라 MoveNext()의 switch문과 같이 특정한 상태가 되면 current값을 변경하도록 하는 내용이 MoveNext에 구현된다. 이렇게 생성된 클래스는 MoveNext 메서드를 호출하여 상태 기계를 제어하고, IEnumerable<T> 및 IEnumerator<T> 인터페이스를 구현하여 foreach 문 등에서 사용할 수 있게 된다.
💡 예를 들어, IEnumerator를 반환하는 메서드를 만들 때, yield return 4; yield return 8; 이렇게 두 개의 yield return문을 포함하고 있으면 C# 컴파일러는 MoveNext에 두 개의 케이스와 상태를 가지는 IEnumerator를 만들어서 반환한다.
💡 IEnumerable을 반환하는 메서드를 만들 때, yield return 4; yield return 8; 이렇게 두 개의 yield return문이 있으면 C# 컴파일러는 MoveNext에 두 개의 케이스와 상태를 가지는 IEnumerator를 만들어서 반환할 수 있는 IEnumerable을 만들어서 반환한다.
IEnumerator는 하나의 객체를 생성해서 리턴하므로 한 번 호출된 후 다른 타이밍에 또 호출이되면 이미 생성되어 있던 객체를 반환하는 반면, IEnumerable을 반환하는 메서드의 경우에는 여러 번 호출되면 각각 다른 IEnumerator 객체를 만들어서 반환하게 된다.
즉, IEnumerable을 반환하는 메서드는 여러 번 호출될 때마다 독립적인 IEnumerator 객체를 생성하여 각각의 상태를 유지한다. 이렇게 하면 여러 개의 반복이 서로에게 영향을 미치지 않고 독립적으로 동작할 수 있다.
반면에 IEnumerator을 반환하는 메서드는 보통 하나의 IEnumerator 객체를 반환하며, 이 객체는 해당 시점에서의 상태를 기억하고 유지한다. 따라서 같은 코루틴이 여러 번 호출될 때에는 독립적인 상태를 유지하지 않는다.
코루틴의 경우 IEnumerator를 반환하도록 구현하지만 여러 번 호출하면 각각의 상태를 갖는 이유는 StartCoroutine메서드를 통해 호출되기 때문으로 이해함
상세하게 설명된 글이 많지 않아서 이해하는 데 너무 오래 걸렸다..
사실 지금 이해한 것이 맞는지 확인도 해야 하는데,,
아무튼 처음 보았던 반복기 예시의 출력 결과물까지는 이해를 했다! (다행)
이제 Coroutine 클래스와 Wait ~ 클래스들, 그리고 코루틴과 성능 등의 관계만 더 공부하면 된다
IEnumerator 인터페이스 (System.Collections)
제네릭이 아닌 컬렉션을 단순하게 반복할 수 있도록 지원합니다.
learn.microsoft.com
'👾 내일배움캠프 > 🎮 TIL & WIL' 카테고리의 다른 글
내일배움캠프 65일차 TIL - 씬 전환 (1) | 2023.11.15 |
---|---|
내일배움캠프 61일차 TIL - 델리게이트 / 이벤트 / 람다 (0) | 2023.11.09 |
내일배움캠프 60일차 TIL - 플레이어와 주방기구 상호작용 (1) | 2023.11.08 |
내일배움캠프 55일차 TIL - InputSystem과 가상 조이스틱 (On-Screen Button / On-Screen Stick) (0) | 2023.10.31 |
내일배움캠프 54일차 TIL - struct와 class (0) | 2023.10.29 |