PART 8 - 강의 2/6
AsyncTask 활용
간편한 비동기 작업 실행과 결과 처리
01
AsyncTask 기본
가장 간단한 비동기 실행 방법
AsyncTask는 지정한 스레드에서 람다 함수를 실행하는 가장 간단한 방법입니다. 헤더 하나만 포함하면 바로 사용할 수 있습니다.
C++
#include "Async/Async.h"
// 백그라운드 스레드에서 작업 실행
AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this]()
{
// 무거운 계산 수행 (백그라운드 스레드)
int32 Result = PerformHeavyCalculation();
// Game Thread로 결과 전달
AsyncTask(ENamedThreads::GameThread, [this, Result]()
{
// UI 업데이트 등 Game Thread 작업
OnCalculationComplete(Result);
});
});
Named Threads
GameThread
RenderThread (Actual)
RHIThread
AnyThread
AnyBackgroundThreadNormalTask
AnyBackgroundHiPriTask
02
Async 함수와 TFuture
결과를 반환하는 비동기 작업
C++
#include "Async/Async.h"
// 결과를 반환하는 비동기 작업
TFuture<int32> Future = Async(EAsyncExecution::ThreadPool, []()
{
// 무거운 계산
return ExpensiveCalculation();
});
// 방법 1: 결과 대기 (블로킹)
int32 Result = Future.Get(); // 완료될 때까지 대기
// 방법 2: 완료 여부 확인
if (Future.IsReady())
{
int32 Result = Future.Get();
}
// 방법 3: 완료 콜백 (논블로킹, 권장)
Future.Then([](int32 Result)
{
// 결과 처리 (백그라운드 스레드에서 실행!)
UE_LOG(LogTemp, Log, TEXT("Result: %d"), Result);
});
EAsyncExecution 옵션
C++
// 스레드 풀 사용 (기본, 권장)
Async(EAsyncExecution::ThreadPool, []() { ... });
// Task Graph 사용
Async(EAsyncExecution::TaskGraph, []() { ... });
// 전용 스레드 생성 (비권장, 오버헤드 큼)
Async(EAsyncExecution::Thread, []() { ... });
// Large Thread Pool (대용량 작업)
Async(EAsyncExecution::LargeThreadPool, []() { ... });
03
실전 패턴
자주 사용하는 비동기 패턴
Fire and Forget
결과가 필요 없는 백그라운드 작업
AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, []()
{
// 로깅, 통계 전송 등
SendAnalytics();
});
Compute and Return
계산 후 Game Thread에서 결과 처리
TFuture<FData> Future = Async(EAsyncExecution::ThreadPool, []()
{
return ComputeData();
});
Future.Then([this](FData Data)
{
AsyncTask(ENamedThreads::GameThread, [this, Data]()
{
ProcessData(Data);
});
});
파일 I/O 예제
C++
void UMySubsystem::LoadDataAsync(const FString& FilePath)
{
// 경로를 값으로 캡처
Async(EAsyncExecution::ThreadPool, [FilePath, this]()
{
FString FileContent;
// 파일 읽기 (백그라운드)
if (FFileHelper::LoadFileToString(FileContent, *FilePath))
{
// 파싱 (백그라운드)
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader =
TJsonReaderFactory<>::Create(FileContent);
if (FJsonSerializer::Deserialize(Reader, JsonObject))
{
// Game Thread에서 결과 처리
AsyncTask(ENamedThreads::GameThread, [this, JsonObject]()
{
ProcessJsonData(JsonObject);
});
}
}
});
}
04
취소 가능한 비동기 작업
작업 중단 처리
C++
UCLASS()
class UMyAsyncLoader : public UObject
{
GENERATED_BODY()
public:
void StartAsyncWork()
{
// 취소 플래그 초기화
bCancelRequested = false;
Async(EAsyncExecution::ThreadPool, [this]()
{
for (int32 i = 0; i < 1000; i++)
{
// 취소 확인
if (bCancelRequested)
{
UE_LOG(LogTemp, Warning, TEXT("Work cancelled"));
return;
}
// 작업 수행
DoWorkUnit(i);
}
// 완료 콜백
AsyncTask(ENamedThreads::GameThread, [this]()
{
OnWorkComplete();
});
});
}
void CancelWork()
{
bCancelRequested = true;
}
private:
// Atomic으로 스레드 안전하게
FThreadSafeBool bCancelRequested;
};
취소 패턴
FThreadSafeBool을 사용하면 락 없이 안전하게 취소 플래그를 확인할 수 있습니다. 긴 작업에서는 주기적으로 취소 여부를 확인하세요.
05
주의사항
흔한 실수 피하기
주의사항
- UObject 접근 금지 - 백그라운드 스레드에서 UObject 접근 시 크래시
- 참조 캡처 주의 - 지역 변수 참조 캡처 시 댕글링 위험
- this 캡처 주의 - 객체 파괴 후 콜백 실행 시 크래시
- 너무 많은 작업 - 스레드 풀 고갈 주의
C++ - 안전한 this 캡처
// 약한 참조로 안전하게 처리
TWeakObjectPtr<UMyObject> WeakThis(this);
Async(EAsyncExecution::ThreadPool, [WeakThis]()
{
// 백그라운드 작업
FData Result = ComputeData();
AsyncTask(ENamedThreads::GameThread, [WeakThis, Result]()
{
// 객체가 아직 유효한지 확인
if (UMyObject* This = WeakThis.Get())
{
This->ProcessResult(Result);
}
});
});
SUMMARY
핵심 요약
핵심 포인트
- AsyncTask - 지정 스레드에서 람다 실행, Fire-and-Forget
- Async + TFuture - 결과를 반환하는 비동기 작업
- Future.Then() - 논블로킹 결과 처리
- EAsyncExecution::ThreadPool - 기본 권장 옵션
- TWeakObjectPtr - 안전한 UObject 참조
- FThreadSafeBool - 취소 플래그용
PRACTICE
도전 과제
배운 내용을 직접 실습해보세요
실습 1: AsyncTask로 비동기 작업 실행
AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, Lambda)를 사용하여 RPG의 대규모 데이터 계산(경로 탐색, 시야 확인)을 백그라운드에서 수행하세요.
실습 2: FAsyncTask로 재사용 가능한 태스크
FNonAbandonableTask를 상속하여 RPG 월드 청크 생성 태스크를 구현하세요. FAsyncTask
심화 과제: Game Thread로 결과 전달
백그라운드 태스크 완료 후 AsyncTask(ENamedThreads::GameThread, Callback)를 사용하여 결과를 Game Thread에서 안전하게 처리하세요. UObject 접근은 반드시 Game Thread에서 하도록 보장하세요.