코루틴과 비동기 프로그래밍

이준호·2023년 12월 25일
0
post-custom-banner

코루틴과 비동기 프로그래밍



📌 동기식 vs 비동기식 프로그래밍

동기식 프로그래밍

코드가 작성된 순서대로 순차적으로 실행된다.

  • 간단한 작업에 적합하다.

  • 시간이 오래걸리는 작업에는 부적합.

void LoadData()
{
    // 파일에서 데이터를 읽는 시간이 오래 걸리는 작업
    string data = ReadFile("data.txt");
    ProcessData(data);
}

비동기식 프로그래밍

주 실행 흐름을 차단하지 않고 작업을 처리

  • 게임의 반응성을 유지하면서 백그라운드에서 데이터를 로드한다.
async void LoadDataAsync()
{
    // 데이터를 비동기적으로 읽어오는 작업
    string data = await ReadFileAsync("data.txt");
    ProcessData(data);
}











📌 Unity에서의 비동기 프로그래밍

Async, Await

  • Async : 메서드가 비동기적으로 작동할 수 있음을 나타낸다.

  • Await : 비동기 작업이 완료될 때까지 현재 메서드의 실행을 일시 중단하고, 완료되면 실행을 계속한다.

using System.Threading.Tasks;
using UnityEngine;

public class AsyncExample : MonoBehaviour
{
    async void Start()
    {
        Debug.Log("데이터 로딩 시작");
        await LoadDataAsync();
        Debug.Log("데이터 로딩 완료");
    }

    async Task LoadDataAsync()
    {
        // 예를 들어, 여기서 외부 API로부터 데이터를 비동기적으로 로딩할 수 있습니다.
        await Task.Delay(2000); // 2초간 대기를 시뮬레이션
        // 데이터 처리 로직
    }
}

Task와 Async / Await

고급 사용법? 비동기 작업을 병렬로 실행하여 여러 작업을 동시에 처리하기

public class AdvancedAsyncExample : MonoBehaviour
{
    async void Start()
    {
        Task task1 = PerformTaskAsync("Task 1", 3000);
        Task task2 = PerformTaskAsync("Task 2", 2000);

        await Task.WhenAll(task1, task2);

        Debug.Log("모든 작업 완료");
    }

    async Task PerformTaskAsync(string taskName, int delay)
    {
        Debug.Log($"{taskName} 시작");
        await Task.Delay(delay);
        Debug.Log($"{taskName} 완료");
    }
}

Async / Await 예외처리

고급 사용법? 비동기 작업 중 발생하는 예외를 예쁘게 처리하기

async Task PerformTaskWithExceptionHandlingAsync()
{
    try
    {
        // 여기서 예외가 발생할 수 있는 비동기 작업을 수행합니다.
        await Task.Delay(1000);
        throw new System.Exception("에러 발생");
    }
    catch (System.Exception e)
    {
        Debug.LogError($"비동기 작업 중 에러 발생: {e.Message}");
    }
}

비동기 프로그래밍 사례

  • 이미지 처리, 데이터 로딩, 네트워크 통신

  • 예) 대규모 게임맵을 로딩할 때, 비동기적으로 세그먼트를 로드하여 사용자가 기다리는 시간을 최소화 한다.

public async void LoadMapSegmentsAsync()
{
    List<Task> loadingTasks = new List<Task>();

    for (int i = 0; i < numberOfSegments; i++)
    {
        loadingTasks.Add(LoadSegmentAsync(i));
    }

    await Task.WhenAll(loadingTasks);

    Debug.Log("모든 세그먼트 로드 완료.");
}

async Task LoadSegmentAsync(int segmentIndex)
{
    // 게임 맵의 각 세그먼트를 비동기적으로 로딩하는 로직
    await Task.Delay(1000); // 로딩 시간 시뮬레이션
    Debug.Log($"Segment {segmentIndex} loaded.");
}

성능과 메모리 관련

  • 제한된 자원 내에서 고려할 것

  • 메모리 누수나 성능 저하에 영향을 미친다.

  • 비동기 작업은 늘 모니터링을 해야한다.

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

async Task LoadDataAsync(CancellationToken cancellationToken)
{
    if (cancellationToken.IsCancellationRequested)
    {
        Debug.Log("비동기 작업 취소됨");
        return;
    }

    // 데이터 로딩 로직
    await Task.Delay(2000, cancellationToken);
}

// 작업 취소 시
void CancelLoading()
{
    cancellationTokenSource.Cancel();
}











📌 Unity에서의 코루틴 프로그래밍

Coroutine

  • 일반 함수와 달리 여러 프레임에 걸쳐서 실행될 수 있고, 일시 중단하고 다시 시작할 수도 있다.

  • 시간 지연, 비동기적 작업 처리, 순차적 이벤트 처리

public class FadeScript : MonoBehaviour
{
    void Start()
    {
        // 코루틴 시작
        StartCoroutine(FadeOut());
    }

    IEnumerator FadeOut()
    {
        float fadeDuration = 3f;
        float elapsedTime = 0f;

        while (elapsedTime < fadeDuration)
        {
            elapsedTime += Time.deltaTime;
            float alpha = 1 - (elapsedTime / fadeDuration);
            // 여기서는 예를 들어 게임 오브젝트의 투명도를 조절하는 로직이 들어갈 것임.
            yield return null; // 다음 프레임까지 기다립니다.
        }
    }
}
IEnumerator ExampleCoroutine()
{
    Debug.Log("코루틴 시작");
    yield return new WaitForSeconds(2);
    Debug.Log("2초 후");
}

코루틴과 게임 루프

  • 코루틴은 게임의 주된 실행 흐름을 방해하지 않으면서 별도의 실행흐름을 만들어낸다. (완전히 독립적이라는 얘기는 아니다.)
IEnumerator GameLoopCoroutine()
{
    while (true) // 무한 루프를 사용하여 게임 루프를 시뮬레이션
    {
        yield return StartCoroutine(PerformTask());
        // 다른 게임 루프 태스크들
    }
}

IEnumerator PerformTask()
{
    // 특정 태스크 수행
    yield return new WaitForSeconds(1); // 1초 대기
    // 태스크 완료 후 로직
}

동시 실행 코루틴

  • 코루틴 여러개가 동시에 시작하고, 각각 독립적으로 실행될 수 있다.
void Start()
{
    StartCoroutine(CoroutineA());
    StartCoroutine(CoroutineB());
}

IEnumerator CoroutineA()
{
    // Coroutine A의 로직
    yield return new WaitForSeconds(2);
    // Coroutine A 완료 후 로직
}

IEnumerator CoroutineB()
{
    // Coroutine B의 로직
    yield return new WaitForSeconds(3);
    // Coroutine B 완료 후 로직
}

연속 실행 코루틴

  • 코루틴 하나가 완료되면 다음 코루틴이 시작되는 형태로 구현도 가능하다. 순차적 실행 코루틴.
IEnumerator SequentialCoroutines()
{
    yield return StartCoroutine(Task1());
    yield return StartCoroutine(Task2());
    // 다음 태스크
}

IEnumerator Task1()
{
    // Task 1의 로직
    yield return new WaitForSeconds(2); // 대기
}

IEnumerator Task2()
{
    // Task 2의 로직
    yield return new WaitForSeconds(3); // 대기
}

코루틴과 비동기적 프로그래밍의 혼합

  • 대규모 게임환경에서 데이터 로딩이 오래걸릴 경우 비동기 프로그래밍과 코루틴을 사용해 해결해보자.
IEnumerator LoadGameData()
{
    var loadDataTask = LoadDataAsync(); // 비동기 데이터 로딩 시작
    yield return new WaitUntil(() => loadDataTask.IsCompleted); // 로딩 완료 대기

    if (loadDataTask.IsFaulted)
    {
        // 에러 처리
    }
    else
    {
        // 데이터 처리
    }
}

async Task LoadDataAsync()
{
    // 데이터 비동기 로딩 로직
    await Task.Delay(1000); // 비동기 로딩 시뮬레이션
}
profile
No Easy Day
post-custom-banner

0개의 댓글