Home [Unity] Coroutine은 비동기가 아니다
Post
Cancel

[Unity] Coroutine은 비동기가 아니다

Coroutine의 기본적인 개념과 사용법은 알고 있다는 가정 하에 작성한 글입니다.

처음에 Unity를 독학했을 때 Coroutine(이하 코루틴)은 유니티에서 비동기 처리를 지원하는 메소드인 줄 알았다.

하지만 이 글의 제목처럼 코루틴은 실제로 비동기 처리를 하는 것이 아닌 하나의 스레드(싱글 스레드)에서 비동기처럼 작동하는 메소드이다.

유니티 공식 문서에서도 위 내용이 작성되어 있는데 공식 문서에서는 아래와 같이 표현하고 있다.

코루틴은 스레드가 아니라는 점을 명심해야 합니다.
코루틴의 동기 작업은 여전히 메인 스레드에서 실행됩니다.
메인 스레드에 소요되는 CPU 시간을 줄이려면
다른 스크립트 코드에서와 마찬가지로 코루틴의 작업 차단을 방지하는 것이 중요합니다.
Unity 내에서 다중 스레드 코드를 사용하려면 C# 잡 시스템을 고려하십시오.

실제로 하나의 스레드에서 작동하는지 간단한 테스트를 진행해 보자

테스트를 위한 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Test : MonoBehaviour
{
    [SerializeField]
    Transform _cube;


    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(COR_CubeMovement());
        StartCoroutine(COR_DelayTest());
    }

    IEnumerator COR_CubeMovement()
    {
        float time = 0f;

        float duration = 1 / 3f;
        while(time < 1f)
        {
            _cube.transform.position = Vector3.Lerp(Vector3.zero, Vector3.forward * 5f, time);
            time += Time.deltaTime * duration;
            yield return null;
        }
    }

    IEnumerator COR_DelayTest()
    {
        for (int i = 0; i < int.MaxValue; i++)
        {
            for (int j = 0; j < int.MaxValue; j++)
            {

            }
            Debug.Log(i);
            yield return null;
        }
    }
}

코드를 보면,

COR_DelayTest() 메소드에서 yield return null이 되기 전까지 많은 양의 반복문이 돌게 된다.

즉,

코루틴이 싱글 스레드에서 비동기인 척하는 처리 방식이 맞다면 yield return null이 될 때까지 많은 횟수의 반복문을 실행해야 하고 그렇기 때문에 큐브가 이동 처리를 하기 전까지 게임이 멈추는 듯한 렉이 발생할 것이다.

결과에서 볼 수 있듯이 반복문이 int.MaxValue 횟수만큼 돌고 yield return null을 통해 실행을 일시 정지하고 큐브 이동이 진행되기 때문에 큐브가 뚝뚝 끊기면서 이동하는 것을 볼 수 있다.

이것을 통해 코루틴은 스레드처럼 비동기로 돌아가는 것이 아닌 하나의 스레드(싱글 스레드)에서 비동기처럼 작동하는 것임을 알 수 있다.

이 문제를 해결하려면 공식 문서에서 제안하는 “JOB(잡)” 시스템 또는 C#의 Async를 사용하면 된다.

아래 코드는 Async를 사용하여 만들어본 테스트 코드이다.

테스트를 위해 만든 임시 코드임으로 올바른 사용 방법이 아닐 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class AsyncTest : MonoBehaviour
{
    [SerializeField]
    Transform _cube;


    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(COR_CubeMovement());
        DelayTest();
    }

    async void DelayTest()
    {
        await Task.Run(() =>
        {
            for (int i = 0; i < int.MaxValue; i++)
            {
                for (int j = 0; j < int.MaxValue; j++)
                {
                    
                }
                Debug.Log(i);
                Task.Delay(10);
            }
        });

    }

    IEnumerator COR_CubeMovement()
    {
        float time = 0f;

        float duration = 1 / 3f;
        while(time < 1f)
        {
            _cube.transform.position = Vector3.Lerp(Vector3.zero, Vector3.forward * 5f, time);
            time += Time.deltaTime * duration;
            yield return null;
        }
    }    
}

참고 자료

코루틴 - Unity 매뉴얼

This post is licensed under CC BY 4.0 by the author.