UE5 비동기 & 태스크 시스템
Unreal Engine은 게임 스레드 외에 다수의 워커 스레드를 운영합니다. 무거운 연산을 백그라운드 스레드로 분산하면 게임 스레드의 프레임 시간을 절약할 수 있습니다. UE5에서는 TaskGraph, AsyncTask, UE::Tasks 세 가지 주요 API를 제공합니다.
UE 스레드 구조
섹션 제목: “UE 스레드 구조”| 스레드 | 역할 |
|---|---|
| Game Thread | Actor Tick, 입력 처리, 블루프린트 실행 |
| Render Thread | 렌더 커맨드 생성 |
| RHI Thread | GPU 커맨드 제출 |
| Worker Threads | TaskGraph 작업 실행 풀 |
UObject·AActor·UWorld에 대한 직접 접근은 반드시 Game Thread에서만 허용됩니다.
AsyncTask — 가장 간단한 스레드 전환
섹션 제목: “AsyncTask — 가장 간단한 스레드 전환”// 백그라운드 스레드에서 실행AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [](){ // 무거운 연산 (파일 I/O, 경로 탐색 등) TArray<FString> Results = LoadHeavyData();
// 결과를 Game Thread로 전달 AsyncTask(ENamedThreads::GameThread, [Results = MoveTemp(Results)]() { // Game Thread에서 Actor 업데이트 GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Green, FString::Printf(TEXT("로드 완료: %d개"), Results.Num())); });});ENamedThreads 주요 값
섹션 제목: “ENamedThreads 주요 값”| 값 | 스레드 |
|---|---|
GameThread | 메인 게임 스레드 |
AnyBackgroundThreadNormalTask | 워커 스레드 (일반 우선순위) |
AnyBackgroundHiPriTask | 워커 스레드 (높은 우선순위) |
AnyThread | 여유 스레드 자동 선택 |
TaskGraph — 의존성 기반 태스크
섹션 제목: “TaskGraph — 의존성 기반 태스크”복수의 태스크 간 의존성(선후 관계)을 설정할 때 사용합니다.
#include "Async/TaskGraphInterfaces.h"
// 태스크 클래스 정의class FMyTask{public: static const TCHAR* GetTaskName() { return TEXT("MyHeavyTask"); }
FORCEINLINE static TStatId GetStatId() { RETURN_QUICK_DECLARE_CYCLE_STAT(FMyTask, STATGROUP_TaskGraphTasks); }
static ENamedThreads::Type GetDesiredThread() { return ENamedThreads::AnyBackgroundThreadNormalTask; }
static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) { // 실제 작업 HeavyComputation(); }};
// 태스크 디스패치 및 완료 대기FGraphEventRef Task = TGraphTask<FMyTask>::CreateTask() .ConstructAndDispatchWhenReady();
// 의존 태스크 — Task 완료 후 실행FGraphEventArray Prerequisites = { Task };FGraphEventRef DependentTask = TGraphTask<FFollowUpTask>::CreateTask(&Prerequisites) .ConstructAndDispatchWhenReady();UE::Tasks — UE 5.1+ 권장 API
섹션 제목: “UE::Tasks — UE 5.1+ 권장 API”UE 5.1부터 도입된 경량 태스크 API로, TaskGraph보다 간결합니다.
#include "Tasks/Task.h"
using namespace UE::Tasks;
void AMyActor::RunAsyncWork(){ // 백그라운드 태스크 실행 FTask Task = Launch(TEXT("HeavyWork"), []() -> int32 { return PerformHeavyCalculation(); });
// 태스크 완료 후 콜백 (파이프라인) Launch(TEXT("ProcessResult"), [Task]() { int32 Result = Task.GetResult(); // 주의: 여기도 워커 스레드 — UObject 접근 불가 }, Prerequisites(Task));}태스크 완료 대기 (Blocking)
섹션 제목: “태스크 완료 대기 (Blocking)”FTask Task = Launch(TEXT("Work"), []() { HeavyWork(); });
// Game Thread에서 블로킹 대기 — 프레임 드롭 주의Task.Wait();
// 또는 완료 여부만 확인if (Task.IsCompleted()) { ... }Async / Await 스타일 (Coroutine)
섹션 제목: “Async / Await 스타일 (Coroutine)”UE5는 C++ 코루틴을 지원하지 않지만, 플러그인 UE5Coro 또는 PromiseAPI를 활용하면 유사한 패턴을 구현할 수 있습니다. 공식적으로는 Latent Action이 대안입니다.
// Latent Action — Blueprint에서도 사용 가능한 비동기 노드UFUNCTION(BlueprintCallable, meta=(Latent, LatentInfo="LatentInfo"))void LoadDataAsync(FLatentActionInfo LatentInfo, FString Path, FString& OutResult){ if (UWorld* World = GetWorld()) { FLatentActionManager& Manager = World->GetLatentActionManager(); if (!Manager.FindExistingAction<FMyLatentAction>( LatentInfo.CallbackTarget, LatentInfo.UUID)) { Manager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FMyLatentAction(Path, OutResult, LatentInfo)); } }}스레드 안전 규칙 요약
섹션 제목: “스레드 안전 규칙 요약”// ✅ 워커 스레드에서 안전한 작업- 순수 수학 연산- TArray / TMap 로컬 복사본 조작- FString 생성·파싱- 파일 I/O (IPlatformFile)
// ❌ 워커 스레드에서 금지- UObject 생성·접근 (NewObject, GetWorld 등)- Actor Spawn / Destroy- UE_LOG (스레드 안전하지 않은 버전)- Slate / UMG UI 접근UE::Tasks::FPipe — 순서 보장 직렬 파이프
섹션 제목: “UE::Tasks::FPipe — 순서 보장 직렬 파이프”같은 파이프에 제출된 태스크는 FIFO 순서로 순차 실행됩니다. 뮤텍스 없이 공유 리소스를 안전하게 직렬화할 때 사용합니다.
#include "Tasks/Task.h"
using namespace UE::Tasks;
class FAssetProcessor{ FPipe ProcessingPipe{TEXT("AssetPipeline")};
public: void EnqueueAsset(const FString& AssetPath) { // 같은 파이프의 태스크는 순서대로 실행 — 동시 실행 없음 ProcessingPipe.Launch(TEXT("ProcessAsset"), [AssetPath]() { LoadAndCacheAsset(AssetPath); // 공유 캐시 안전하게 수정 }); }};TFuture / TPromise — 비동기 값 전달
섹션 제목: “TFuture / TPromise — 비동기 값 전달”워커 스레드에서 계산한 값을 게임 스레드로 전달하는 패턴입니다. TPromise는 값의 공급자, TFuture는 소비자입니다.
#include "Async/Future.h"
void AMyActor::StartHeavyTask(){ TPromise<int32> Promise; TFuture<int32> Future = Promise.GetFuture();
// 백그라운드에서 연산 후 값 설정 AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [Promise = MoveTemp(Promise)]() mutable { int32 Result = PerformHeavyCalculation(); Promise.SetValue(Result); // Future 완성 });
// 결과 도착 시 콜백 (논블로킹) Future.Then([](TFuture<int32> InFuture) { int32 Result = InFuture.Get(); // 결과를 게임 스레드로 넘겨야 UObject 접근 가능 AsyncTask(ENamedThreads::GameThread, [Result]() { GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Cyan, FString::Printf(TEXT("연산 결과: %d"), Result)); }); });}