Unity 비동기와 await

전경진·2024년 8월 12일
0

1. 비동기와 동기

동기 방식 :

동기 코드에서는 모든 작업이 직렬로 실행된다. 이는 이전 작업이 끝날 때까지 다음 작업이 실행되지 않음을 의미한다.
TraceMemory를 예로 들자면, 플레이어가 프로필 이미지를 설정하면 ImageUploader로직이 Firebase의 Storage로 이미지를 저장하고 URL을 반환하는 동안 프로그램의 나머지 부분은 진행하지 못하게 된다.
즉, 동기 코드에서 긴 작업이 이어지는 동안에는 해당 작업이 진행되는 동안 다른 부분이 멈추게 된다. 때문에 사용자는 이를 보고 "프로그램이 멈췄다."라며 불편함을 느낄 수 있다

비동기 방식 :

동기 코드에서는 모든 작업이 직렬로 실행됐다면, 비동기 방식에서는 코드의 진행이 병렬로 실행된다. 이미지 전송 및 URL 반환을 실행하더라도 이 부분을 비동기 방식으로 실행한다면 다른 코드와 병렬로 진행되므로 메인 스레드나 다른 중요한 작업은 멈추지 않고 계속해서 실행된다.

2. 비동기에서의 await 사용 여부에 따른 차이.

await 사용 :

비동기 메서드 안에서만 사용되며, 비동기 메서드 안에서 또 다시 비동기 메서드가 호출 되었을 때, 그 비동기 메서드의 결과를 기다리는가 기다리지 않는가를 결정하기 위해 사용된다.

비동기 메서드 안에서만 사용되는 이유는, 밖에서 메서드의 결과를 기다리고자 한다면 굳이 await가 아닌 동기 방식을 채택하면 되기 때문이다.
즉, 비동기 메서드 안에서 A라는 비동기 메서드에 await를 사용한다면 A가 끝날 때까지 기다렸다가 이후의 함수를 실행한다.

await 미사용 :

비동기 메서드 안에서 A라는 또 다른 비동기 메서드에 await를 사용하지 않는다면, A가 비동기적으로 실행되며 그 이후의 코드들은 A의 종료를 기다리지 않고 실행된다.

3. ContinueWith

ContinueWith은 비동기 메서드의 끝에 붙어 비동기 메서드가 실행된 다음으로 실행될 코드의 부분을 정의하는 데 사용한다.

public Task<System.Uri> UploadImage(byte[] imageBytes, string fileName)
{
    StorageReference storageRef = storage.RootReference;
    StorageReference imageRef = storageRef.Child("images/" + fileName);

    // 이미지를 업로드하고, 작업이 완료된 후 다운로드 URL을 반환
    return imageRef.PutBytesAsync(imageBytes).ContinueWith((Task<StorageMetadata> task) => {
        if (task.IsFaulted || task.IsCanceled)
        {
            Debug.LogError(task.Exception.ToString());
            return null; // 문제가 발생하면 null 반환
        }
        else
        {
            Debug.Log("Upload successful");
            // 업로드가 성공하면 다운로드 URL을 반환
            return imageRef.GetDownloadUrlAsync().Result;
        }
    });
}

위의 코드는 MemoryProject의 ImageUploader 클래스의 메서드이다. 위에서 쓰인

imageRef.PutBytesAsync(imageBytes).ContinueWith((Task<StorageMetadata> task) => {...})

는 비동기 메서드인 PutBytesAsync(imageBytes)를 실행하고 그 다음으로 실행할 하나의 람다함수를 정의한다.

UploadImage 메서드는 비동기 방식이 아니기 때문에 PutBytesAsync의 동기 방식의 대체 함수를 사용한다면 ContinueWith을 사용할 필요가 없다. 왜냐하면 애초에 직렬 방식으로 진행되는 함수를 사용한다면 순서를 고지하는 ContinueWith을 사용하지 않더라도 순서대로 진행되기 때문이다. 그렇지만 PutBytesAsync는 Firebase에서 제공하는 메서드이기 때문에 동기 방식의 대체 함수가 존재하지 않는다. 따라서, 위의 코드에서는 비동기 방식의 메서드가 사용되었고, 그 와중에 메서드의 종료 이후에 실행될 코드가 존재하기 때문에 ContinueWith을 사용했다. 즉, 비동기 방식으로 작동하는 함수가 종료되고 나서 실행되어야만 하는 로직을 구성해야 할 때 ContinuWith을 사용한다.

ContinueWith은 앞선 내용인 await와도 함께 사용할 수 있다.

4. await와 Task

async 메서드의 반환 타입은 Task이거나 void 이어야 한다.
이때, 메서드를 호출할 때 'await'를 사용하여 호출한다면 비동기 작업이 끝날 때까지 기다림으로써 Task가 아닌 순수 데이터 타입의 결과를 얻을 수 있다.

public async Task<bool> UploadImage(byte[] imageBytes, string fileName)
{
   StorageReference storageRef = storage.RootReference;
   StorageReference imageRef = storageRef.Child("images/" + fileName);

   try
   {
       // 이미지를 업로드하고, 작업이 완료된 후 결과를 반환
       await imageRef.PutBytesAsync(imageBytes);
       Debug.Log("Upload successful");
       return true;
   }
   catch (Exception e)
   {
       Debug.LogError(e.ToString());
       return false;
   }
}

TraceMemory 프로젝트의 ImageUploader 클래스의 일부 메서드

bool isSuccessed = await imageUploader.UploadImage(profileImage.EncodeToPNG(), fileName); // await로 비동기 작업 처리

TraceMemory 프로젝트의 ButtonManager 클래스의 SetImage 메서드의 일부

위의 두 코드를 보면, UploaderImage의 반환 타입은 Task<bool>임에도 불구하고 await를 붙여 호출하니 bool 값으로 받을 수 있다.

  • 이전에는 위와 같은 코드를 사용하지 않았다. 3번 항목에서 기재한 바와 같이 ContinueWith으로 실행하도록 했다. 하지만, 이를 다른 클래스의 메서드에서 Task<type>.Result로 결과를 반환하게 한다면 이미지 업로드가 끝날 때까지 결과를 기다리게 되므로, 프로그램이 대기 상태에 빠지는 결과를 불러 일으킨다. 이에 결과 값을 비동기적으로 받도록 수정하였다.
profile
전경진입니다.

0개의 댓글