https://wonseok1112.tistory.com/27
동기/비동기와 스레드/멀티스레드의 개념에 대해 설명한 글을 보고 오시면 이해하는데 많은 도움이 됩니다.
유니티에서의 대부분 로직은 동기 방식으로 작성되며, 흐름(Flow, Stream)을 관리하거나 시간을 조절해 시행해야하는 작업은
코루틴(Coroutine)이라는 기능을 활용하여 구현된다.
비동기 방식을 유니티(Unity)에서 언제 사용하는가?
사실 게임 개발에서는 비동기(async/await)를 쓸 일이 적다. 비동기를 써야 하는 상황이 많지 않기 때문이다.
비동기를 써야 하는 상황으로는 네트워크 소켓 통신(서버 통신)을 하는 경우, 용량이 큰 파일을 로드하거나 접근하는 경우 정도가 있다.
코루틴을 쓰면 되지 않나?
유니티는 이미 작업을 다수의 프레임에 분산할 수 있는 코루틴(Coroutine)이라는 기능을 가지고 있는데, 왜 굳이 생소하고 어려운 개념인 async/await를 써야 하나 싶을 수도 있다.
그러나 근본적으로 코루틴은 동기 방식이다. 따라서 무거운 작업을 행하기에는 적절하지 않다.
극단적인 예를 하나 들어보겠다.
IEnumerator Test()
{
yield return null;
while(true)
{
Debug.Log(".");
}
}
탈출이 불가능한 while문 코루틴 로직이다.
이것을 실행한 프로그램은 while문의 굴레에 갇혀 영원히 끝나기만을 기다려야 할 것이다.
따라서 위처럼 작업의 용량 자체가 매우 큰 경우 또는 작업이 언제 끝날지 모르는 경우, 작업의 진행 시간이 매우 길어
블로킹(blocking) 상태가 오래 지속되는 경우에 async/await 비동기 방식을 사용해야 한다.
유니티 async / await 사용 예시
using System.Threading.Tasks;
using UnityEngine;
public class AsyncTest : MonoBehaviour
{
void Start()
{
Debug.Log("Start!!!");
Test();
Debug.Log("1");
}
private async void Test()
{
Debug.Log("2");
await Task.Delay(7000);
Debug.Log("3");
}
}
async와 await를 이용한 비동기식 로직 구현 예시이다.
코드 진행도는 아래와 같다.
- 프로그램 시작 시 "Start!!!"가 출력된다.
- Test함수가 실행된다.
- 2가 출력되고 7초를 기다린다.
- 7초를 기다리는 도중 Test를 호출한 호출자에게 넘어가서 1을 출력한다.
- 7초가 다 지나면 3을 출력한다.
결과값
다른 예시
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.InputSystem;
public class TestLoader : MonoBehaviour
{
[SerializeField] private AssetReference _levelRef;
private List<GameObject> _list = new();
private void Update()
{
if (Keyboard.current.qKey.wasPressedThisFrame)
{
LoadLevel();
}
}
private async void LoadLevel()
{
if (!_levelRef.IsValid())
{
await _levelRef.LoadAssetAsync<GameObject>().Task;
}
var obj = Instantiate(_levelRef.Asset, Vector3.zero, Quaternion.identity) as GameObject;
_list.Add(obj);
}
}
위의 예시는 유니티 어드레서블(addressable)을 사용하여 q키를 눌렀을 때 에셋 레퍼런스를 로드하는 예시이다.
코드 설명
LoadAssetAsync<GameObject>() 메서드로 비동기로 에셋을 로드하고, await 키워드를 사용하여
해당 로드 작업이 완료될때까지 대기한다. Task 속성을 사용하여 비동기 작업을 시작하고,
작업이 완료되면 로드된 에셋을 반환한다.
이 예시 역시 용량이 큰 맵 에셋을 로드하는 코드이므로 비동기 방식을 쓰는 모습을 볼 수 있다.
만약 비동기 방식을 쓰지 않으면, 블로킹이 일어나 맵을 로드하는 동안 메인 스레드가 차단되어 문제가 생긴다.
유니티 어드레서블을 공부하다가 async와 await가 이해가 잘 되지 않아 공부를 했는데, 비동기/동기, 스레드 관련해서도
개념이 이어져있어 공부하는데에 어려움을 겪었다. 그래도 결국 이해가 잘된 것 같아 다행이다.
어드레서블의 상황처럼 앞으로 큰 에셋을 로드할 때 많이 애용해야겠다는 생각이 든다.
'C# 기초' 카테고리의 다른 글
[C#] 배열, 리스트, 딕셔너리 (Array, List, Dictionary) (1) | 2023.12.11 |
---|---|
[C#] delegate(델리게이트)와 event(이벤트) (0) | 2023.11.08 |
[C#][개념] 스레드와 동기/비동기 (0) | 2023.10.31 |
[C#] dynamic과 default 키워드 (1) | 2023.10.30 |
[C#] sealed class (상속 금지)와 partial class (0) | 2023.10.25 |