오늘 다룰 주제는 비동기 처리입니다. 특히, 유저 입력이나 서버 응답을 기다려야 하는 상황에서 async/await
을 활용하는 방법에 대해 포스팅합니다. 우선 동기와 비동기의 차이점에 대해 간략히 설명하겠습니다.
async/await
의 필요성시간이 오래 걸리는 작업(예: 서버 통신, 파일 읽기, 사용자 입력 대기 등)을 메인 스레드를 멈추지 않고 처리하고 싶을 때 async/await
를 사용합니다.
async/await
의 문법 기본 구조public asyn Task<int> AsyncFunction()
{
await Task.Delay(1000); // 1초 대기
return 1020;
}
async
: 이 함수는 비동기 함수임을 선언하며, 내부에서 await
를 사용할 수 있게 합니다.await
: 비동기 작업이 끝날 때까지 논리적으로는 대기하지만, 실제로 스레드는 점유하지 않습니다.await
은 코드 흐름을 잠시 멈추는 것이지, CPU를 점유하거나 블로킹하는 것이 아닙니다.await
뒤에는 Task
가 옵니다.Task<int>
: 이 함수는 비동기적으로 실행되며, 최종적으로 int
결과를 반환하는 Task
객체를 리턴함을 의미합니다.게임 개발에서는 유저에게 어떤 선택을 하도록 유도하는 팝업 UI를 띄우고, 유저가 선택할 때까지 기다린 다음, 그에 따라 행동을 결정하는 경우가 많습니다. 이처럼 외부 입력을 기다렸다가 흐름을 재개하는 구조에 async/await
는 매우 적합합니다.
CheckAbandonItem()을 호출하면:
1. 팝업을 띄우고
2. 유저가 "예"/"아니오"를 누를 때까지 기다렸다가
3. 선택 결과에 따라 분기 처리
public async void AbandonItem(DraggableItem requester, Item item)
{
// 유저의 응답 대기하기
bool abandon = await inventoryView().CheckAbandonItem(item);
// ==== CheckAbandonItem()이 bool 값을 처리하기 전까지 실행되지 않는다 ====
// 유저 응답에 따른 분기 처리
if (abandon)
{
// 아이템을 버린다고 선택한 경우
requester.ConfirmAbandonItem();
controller().AbandonItem(item);
}
else
{
// 아이템을 버리지 않겠다고 선택한 경우
requester.CancelAbandonItem();
}
}
여기서 중요한 포인트는 await
가 유저의 선택이 끝날 때까지 기다렸다가, 그 결과(bool abandon
)를 받은 뒤 다음 로직을 수행한다는 것입니다. 코드 흐름은 마치 동기처럼 보이지만, 내부적으로는 비동기로 작동하여 메인 스레드를 멈추지 않습니다.
public async Task<bool> CheckAbandonItem(Item item)
{
// 가이드 팝업을 띄운다
guidePopup.SetPopupInfo($"{item.details.label} 아이템을\n버리시겠습니까?");
guidePopup.ShowUI();
// Task 생성하기
abandonDecision = new TaskCompletionSource<bool>();
// abandonDecision의 Task가 완료되길 기다리기
// 여기서 완료란, Task<T> 의 T 값을 설정하는 것이다.
return await abandonDecision.Task;
}
private void SetGuidePopupEvents()
{
// 버튼 클릭에 대한 이벤트 등록
guidePopup.SetDynamicPopupEvent(OnClickGuidePopupConfrimBtn, OnClickGuidePopupCancelBtn);
}
private void OnClickGuidePopupConfrimBtn()
{
// abandonDecision Task의 값을 설정하여 작업을 완료시킨다.
abandonDecision?.TrySetResult(true);
// 팝업 닫기
guidePopup.HideUI();
}
private void OnClickGuidePopupCancelBtn()
{
// abandonDecision Task의 값을 설정하여 작업을 완료시킨다.
abandonDecision?.TrySetResult(false);
// 팝업 닫기
guidePopup.HideUI();
}
이 함수는 팝업을 띄우고, TaskCompletionSource<bool>
로 비동기 작업을 생성한 후, 유저가 응답할 때까지 await
을 통해 대기 상태에 진입합니다. 실제 값은 버튼 클릭 이벤트에서 설정됩니다.
return await abandonDecision.Task
여기서 abandonDecision.Task
는 await
을 만나 bool
값이 됩니다. 그러므로, 이 함수는 bool
을 반환하는 것 같지만, 다시 컴파일러가 이를 Task<bool>
로 감싸 반환합니다. async
로 선언된 함수는 항상 Task<T>
를 반환하기 때문입니다.
여기서 사용된 TaskCompletionSource
는 다음과 같습니다.
개발자가 직접 완료시킬 수 있는 Task<T>
를 만드는 클래스입니다. 보통 Task
는 시스템이 실행한 후 완료시켜주지만, TaskCompletionSource
는 개발자가 수동으로 SetResult
, SetException
, SetCanceled
를 호출해줘야 합니다.
메서드 | 설명 |
---|---|
.Task | 우리가 컨트롤할 Task 객체 |
.SetResult(value) | 작업을 성공적으로 종료하고 결과를 설정 |
.TrySetResult(value) | 위와 같지만, 이미 완료된 경우 예외를 발생시키지 않음 |
.SetException(e) | 작업 실패로 완료시키며 예외 전달 |
.SetCanceled() | 작업을 취소된 상태로 완료시킴 |
이 도구는 유저 입력, 서버 응답, 애니메이션 완료 등 외부 이벤트 기반의 작업을 비동기 흐름에 통합할 때 매우 유용합니다.
이처럼 코드를 작성하면, 사용자가 선택할 때까지 메인 스레드는 멈추지 않으면서도, 마치 동기처럼 기다릴 수 있게 됩니다.
이상입니다.