태스크 시스템 레퍼런스

태스크 시스템에서 실행 가능한 여러 태스크에 대한 레퍼런스입니다.

Choose your operating system:

Windows

macOS

Linux

목차

태스크 시스템은 UE::Tasks 네임스페이스에 있습니다. 태스크 시스템을 사용하려면 Tasks/Task.h 라이브러리를 추가해야 합니다. 사용 예시는 Tests/Tasks/TasksTest.cpp 클래스를 참조하면 됩니다. 다음 레퍼런스 표는 태스크 시스템의 핵심 기능의 예시 몇 가지를 보여줍니다.

레퍼런스

설명

TTask<ResultType>

실제 태스크의 핸들입니다. 레퍼런스 카운트로 태스크의 수명을 관리합니다.

  • 태스크는 실행과 동시에 생성됩니다.

  • 마지막 사용자가 보유한 레퍼런스를 해제하더라도, 시스템에서 태스크 실행에 필요한 내부 레퍼런스를 계속 보유하기 때문에 태스크를 해제할 필요는 없습니다.

  • FTask는 결과를 반환하지 않는 태스크(TTask)의 다른 이름입니다.

TTask<ResultType>::IsValid()

함수:

bool TTask<ResultType>::IsValid() const;

태스크 핸들이 태스크를 참조할 경우 true를 반환합니다. 디폴트로 생성된 태스크 핸들은 ‘비어 있으므로' ‘유효하지 않습니다.' 태스크는 실행과 동시에 생성됩니다. 예를 들면 다음과 같습니다.

FTask Task;
check(!Task.IsValid());
Task = Launch(UE_SOURCE_LOCATION, [] {});
check(Task.IsValid());
Task = {}; // reset the task object
check(!Task.IsValid());

Task<ResultType>::Launch

비동기 실행을 위해 태스크를 실행합니다. 아래 코드 예시에서는 실행 기능을 태스크에 사용한 뒤 핸들을 반환합니다.

template<typename TaskBodyType>
TTask<TInvokeResult_T<TaskBodyType>> Launch(
        const TCHAR* DebugName,
        TaskBodyType&& TaskBody,                            
        LowLevelTasks::ETaskPriority Priority =
        LowLevelTasks::ETaskPriority::Normal
);

종속된 태스크가 실행되기 전에 다른 태스크부터 완료해야 합니다. 모든 선행 태스크가 완료되면 자동으로 태스크 실행을 예약합니다.

template<typename TaskBodyType, typename PrerequisitesCollectionType>
TTask<TInvokeResult_T<TaskBodyType>> TTask<ResultType>::Launch(
        const TCHAR* DebugName,
        TaskBodyType&& TaskBody,                            
        PrerequisitesCollectionType&& Prerequisites,
        LowLevelTasks::ETaskPriority Priority =
            LowLevelTasks::ETaskPriority::Normal
);

파라미터:

  • DebugName - 디버거 및 프로파일러에서 태스크를 식별할 때 사용되는 이름입니다. 되도록이면 고유한 이름을 사용합니다. UE_SOURCE_LOCATION 매크로를 사용할 수 있습니다. 이 매크로는 매크로가 사용된 소스 위치에 [Filename]:[Lineno] 스트링을 생성합니다.

  • TaskBody - 비동기적으로 실행되는 호출 가능 오브젝트입니다(예: 람다, 함수 포인터 또는 연산자()가 있는 클래스).

  • Prerequisites - 반복 가능한 TTask의 컬렉션입니다. 결과 타입이 태스크의 결과 타입과 일치할 필요는 없습니다.

  • Priority - 태스크의 실행 순서에 영향을 미치는 태스크 우선순위입니다.

예시:

FTask Prerequisite1 = Launch(UE_SOURCE_LOCATION, []{});
FTask Prerequisite2 = Launch(UE_SOURCE_LOCATION, []{}, ETaskPriority::High);
FTask DependentTask = Launch(UE_SOURCE_LOCATION, []{}, Prerequisites(Prerequisite1, Prerequisite2));

TTask<bool> BoolTask = Launch(UE_SOURCE_LOCATION, []{ return true; });

template<typename... TaskTypes> TPrerequisites<TaskTypes...> Prerequisites(TaskTypes&... Tasks); 는 Prerequisites의 변수 수를 Launch()FTaskEvent::AddPrerequisites() 에 전달하는 헬퍼 함수입니다. 추가 예시로 Launch(), Wait(), GetResult() 태스크를 관찰할 수 있습니다.

TTask<ResultType>::IsCompleted

태스크가 완료되었거나 유효하지 않으면 true를 반환합니다.

bool TTask<ResultType>::IsCompleted() const;

만약 태스크의 실행이 끝나고 모든 중첩된 태스크가 완료되면 태스크가 완료됩니다.

예시:

FTask Task;
check(Task.IsCompleted());
Task = Launch(UE_SOURCE_LOCATION, []{});
Task.Wait();
check(Task.IsCompleted());

추가 예시로 Launch(), Wait(), GetResult() 태스크를 관찰할 수 있습니다.

TTask<ResultType>::Wait

태스크가 완료되거나 타임아웃되기를 기다렸다가 현재 스레드를 블록합니다. 타임아웃 시 false를 반환합니다. 지정된 타임아웃 값보다 대기가 길어질 수 있습니다. Wait() 에서 true를 반환하면 태스크가 완료됩니다. 태스크가 유효하지 않으면 즉시 true를 반환합니다.

bool TTask<ResultType>::Wait(FTimespan Timeout);                
template<typename TaskCollectionType>

bool Wait(const TaskCollectionType& Tasks, FTimespan InTimeout);    

예시:

FTask Task;
Task.Wait(); // 즉시 반환

Task = Launch(UE_SOURCE_LOCATION, []{});
Task.Wait(FTimespan::FromMillisecond(3)); // 태스크가 완료되거나 타임아웃되기를 기다렸다가 블록

FTask AnotherTask = Launch(UE_SOURCE_LOCATION, []{});
TArray<FTask> Tasks{ Task, AnotherTask };
Wait(Tasks); // 모든 태스크가 완료될 때까지 블록

선행 태스크에 의해 블록되었거나 워커 스레드에서 아직 선택하지 않아 태스크 실행이 아직 시작되지 않은 경우, 태스크를 ‘철회'하거나 로컬로 실행할 때까지 대기합니다(인라인). 태스크 실행이 아직 시작되지 않았으므로, 워커 스레드가 태스크를 실행하는 동안 대기 스레드를 블록해야 합니다. 대기 스레드로 태스크를 실행하면 느리지 않고 빠른 프로세스를 진행할 수 있으며 워커 스레드를 침범하지도 않습니다. 태스크 철회 작업은 ‘딥 태스크 철회'라는 태스크 종속성을 따릅니다. 태스크 실행이 선행 태스크에 의해 차단당할 경우, 태스크 철회는 선행 태스크를 철회하고 다시 실행하여 차단을 풀려고 시도합니다. 테스크 실행이 이미 시작했거나 모종의 이유로 태스크 철회에 실패할 경우 대기 블록으로 돌아갑니다.

예시:

FTask Task1 = Launch(UE_SOURCE_LOCATION, []{});
FTask Task2 = Launch(UE_SOURCE_LOCATION, []{});
FTask Task3 = Launch(UE_SOURCE_LOCATION, []{}, Task2);
Task3.Wait();

위 샘플은 세 개의 태스크를 실행하며, 여기서 Task2Task3 의 선행 태스크가 됩니다. Task3 의 완료를 기다리는 동안 Task3, 혹은 Task3 의 선행 태스크인 Task2 를 철회하고 온라인으로 실행할 수도 있지만, Task1 에는 아무 일도 일어나지 않습니다.

TTask<ResultType>::BusyWait

바쁜 대기(Busy Waiting)는 태스크가 완료되기를 기다리는 동안 관련이 없는 다른 태스크를 실행한다는 뜻입니다. 시스템 처리량은 향상되지만 신중하게 사용해야 합니다. 바쁜 대기는 대기 블록보다 오래 걸릴 수 있으며, 지연시간에 민감한 태스크 체인에 영향을 미칠 수 있습니다. 아래 함수에서 태스크는 대기 중인 태스크가 완료될 때까지 실행 준비를 마친 다른 태스크를 실행합니다. 이 태스크는 BusyWait 반환 후 완료됩니다.

void TTask<ResultType>::BusyWait();

아래 코드 샘플에서 대기 중인 태스크가 완료되었거나 대기 시간이 끝날 때까지 실행 준비를 마친 다른 태스크를 실행합니다. 타임아웃 시 false를 반환합니다. 지정된 타임아웃 값보다 대기가 길어질 수 있습니다. 태스크는 BusyWait에서 true를 반환하면 완료됩니다.

bool TTask<ResultType>::BusyWait(FTimespan Timeout);    

template<typename TaskCollectionType>
bool BusyWait(const TaskCollectionType& Tasks,          
FTimespan InTimeout = FTimespan::MaxValue())

관련이 없는 태스크를 실행하기 전에 바쁜 대기를 사용하면 대기 중인 태스크의 철회를 우선적으로 시도합니다.

FTask Task;
Task.BusyWait(); // 즉시 반환

Task = Launch(UE_SOURCE_LOCATION, []{});
Task.BusyWait(); // 태스크가 완료될 때까지 블록. 블록된 동안 다른 태스크 실행 가능

FTask AnotherTask = Launch(UE_SOURCE_LOCATION, []{});
TArray<FTask> Tasks{ Task, AnotherTask };
BusyWait(Tasks, FTimespan::FromMilliseconds(1)); 
// 모든 태스크가 완료되거나 타임아웃을 기다렸다가 선행 행 블록. 블록된 동안 다른 태스크 실행 가능

TTask<ResultType>::GetResult

태스크에서 실행의 결과로 반환한 오브젝트의 레퍼런스를 반환합니다. 태스크의 바디 실행으로 인해 반환된 값입니다.

ResultType& TTask<ResultType>::GetResult();

void가 아닌 ResultType이 있는 태스크에만 존재합니다.

태스크가 완료되면 호출 시 결과를 즉시 반환합니다. 완료되지 않으면 태스크를 진행하는 동안 블록합니다. 태스크 오브젝트가 소멸되면 결과 오브젝트도 소멸됩니다. 이때 태스크 오브젝트의 마지막 레퍼런스가 해제됩니다. 태스크가 유효하지 않으면 어서트를 호출합니다.

예시:

TTask<bool> BoolTask = Launch(UE_SOURCE_LOCATION, []{ return true; });
bool bResult = BoolTask.GetResult();

TTask<int32> IntTask;
// IntTask.GetResult(); - 태스크가 시작되지 않아 유효하지 않으므로 어서트 호출

AddNested()

주어진 태스크를 ‘현재' 태스크(부모 태스크)에 ‘중첩됨'으로 등록합니다. 현재 태스크(Current Task) 는 현재 스레드에서 실행된 태스크입니다.

부모 태스크(Parent Task) 는 모든 중첩 태스크가 완료될 때까지 완료되지 않습니다.

다른 태스크에서 호출되지 않으면 어서트가 호출됩니다.

template<typename TaskType>
void AddNested(const TaskType& Nested);

예시:

FTask ParentTask = Launch(TEXT("Parent Task"),
    []
    {
        FTask NestedTask = Launch(TEXT("Nested Task"), []{});
        AddNested(NestedTask);
    }
);

FTaskEvent

FTaskEvent는 TTask<ResultType> 와 일부 API를 공유합니다. 예를 들어 IsValid(), IsCompleted(), 대기, 바쁜 대기는 API가 같습니다. 이 섹션에서는 FTaskEvent 전용 API만 설명합니다.

레퍼런스 태스크 이벤트

설명

FTaskEvent Constructor

주어진 디버그 이름으로 태스크 이벤트 오브젝트를 생성합니다. TTask와 달리, 태스크 이벤트는 IsValid() == true 생성, 불완전한 IsCompleted() == false 이후 ‘유효한' 태스크가 됩니다. 디버그 이름은 디버그 용도 태스크 이벤트 오브젝트를 식별하는 데 사용됩니다.

explicit FTaskEvent::FTaskEvent(const TCHAR* DebugName);

태스크 이벤트는 소멸되기 전에 반드시 트리거되어야 합니다.

예시:

FTaskEvent TaskEvent{ UE_SOURCE_LOCATION };
check(TaskEvent.IsValid());
check(!TaskEvent.IsCompleted());
TaskEvent.Trigger();
check(TaskEvent.IsCompleted());

주어진 디버그 이름으로 파이프 오브젝트를 생성합니다. 디버그 이름은 디버그용으로 사용되어 파이프 오브젝트를 식별합니다.

FPipe::FPipe(const TCHAR* DebugName);

파이프(Pipe)는 가볍고 복사 및 이동이 불가능한 오브젝트입니다. 파이프 생성은 동적 메모리를 할당하지 않으며, 비용이 많이 드는 프로세스를 수행하지 않습니다.

FTaskEvent::AddPrerequisites

다른 태스크 또는 태스크 이벤트를 선행 태스크로 추가합니다. 태스크 이벤트를 트리거하기 전에만 호출할 수 있습니다. 모든 선행 태스크가 완료되고 태스크 이벤트가 트리거될 때만 ‘완료됨'(‘시그널링') 상태가 됩니다.

template<typename PrerequisitesType>
void FTaskEvent::AddPrerequisites(const PrerequisitesType& Prerequisites);

예시:

FTaskEvent TaskEvent{ TEXT("TaskEvent") };

TArray<FTask> Prereqs
{ 
    Launch(TEXT("Task A"), [] {}), 
    Launch(TEXT("Task B"), [] {}) 
};
TaskEvent.AddPrerequisites(Prereqs);

FTask TaskC = Launch(TEXT("Task C"), [] {});
FTask TaskD = Launch(TEXT("Task D"), [] {});
TaskEvent.AddPrerequisites(Prerequisites(TaskC, TaskD));

TaskEvent.Trigger();

FTaskEvent::Trigger

태스크 이벤트는 트리거되기 전까지 완료되지 않습니다(‘논시그널링'). 태스크 이벤트를 트리거해도 꼭 시그널링되는 것은 아니며, 모든 선행 태스크가 완료되고 태스크 이벤트가 트리거될 때만 완료됩니다.

모든 태스크 이벤트는 반드시 트리거되어야 합니다. 아니면 소멸자가 태스크 이벤트가 완료되지 않았다고 어서트합니다.

void FTaskEvent::Trigger();

FPipe

파이프(Pipe)는 가볍고 복사 및 이동이 불가능한 오브젝트입니다. 파이프 생성은 동적 메모리를 할당하지 않으며, 비용이 많이 드는 프로세스를 수행하지 않습니다.

레퍼런스 이름

설명

FPipe constructor

주어진 디버그 이름으로 태스크 이벤트 오브젝트를 생성합니다. TTask와 달리, 태스크 이벤트는 IsValid() == true 생성, 불완전한 IsCompleted() == false 이후 ‘유효한' 태스크가 됩니다. 디버그 이름은 디버그 용도 태스크 이벤트 오브젝트를 식별하는 데 사용됩니다.

explicit FTaskEvent::FTaskEvent(const TCHAR* DebugName);

태스크 이벤트는 소멸되기 전에 반드시 트리거되어야 합니다.

예시:

FTaskEvent TaskEvent{ UE_SOURCE_LOCATION };
check(TaskEvent.IsValid());
check(!TaskEvent.IsCompleted());
TaskEvent.Trigger();
check(TaskEvent.IsCompleted());

주어진 디버그 이름으로 파이프 오브젝트를 생성합니다. 디버그 이름은 디버그용으로 사용되어 파이프 오브젝트를 식별합니다.

FPipe::FPipe(const TCHAR* DebugName);

파이프(Pipe)는 가볍고 복사 및 이동이 불가능한 오브젝트입니다. 파이프 생성은 동적 메모리를 할당하지 않으며, 비용이 많이 드는 프로세스를 수행하지 않습니다.

FPipe destructor

파이프에 미완료 태스크가 있는지 확인합니다. 파이프 소멸 시 미완료 태스크가 있으면 안 됩니다.

HasWork()

파이프에 미완료 태스크가 있는지 확인합니다. 파이프 소멸 시 미완료 태스크가 있으면 안 됩니다.

bool FPipe::HasWork() const;

WaitUntilEmpty()

이 호출은 파이프의 모든 태스크가 완료될 때까지 블록합니다.

void FPipe::WaitUntilEmpty();

추가 예시는 HasWork() 함수를 참조하세요.

Launch()

파이프에서 태스크를 실행합니다. 여러 태스크를 동일한 파이프에서 실행할 경우, 태스크를 연속적으로 실행하지 않고 각기 다른 워커 스레드에서 실행할 수 있습니다..

template<typename TaskBodyType>
TTask<TInvokeResult_T<TaskBodyType>> FPipe::Launch(
const TCHAR* InDebugName, 
TaskBodyType&& TaskBody,                            
LowLevelTasks::ETaskPriority Priority = LowLevelTasks::ETaskPriority::Default
);

template<typename TaskBodyType, typename PrerequisitesCollectionType>
TTask<TInvokeResult_T<TaskBodyType>> FPipe::Launch(
const TCHAR* InDebugName, 
TaskBodyType&& TaskBody,                        
PrerequisitesCollectionType&& Prerequisites,
LowLevelTasks::ETaskPriority Priority = LowLevelTasks::ETaskPriority::Default
);

IsInContext()

이 파이프에 속한 태스크에서 호출할 경우 true를 반환합니다. 코드 중에 파이프 태스크에 의해 실행 가능한 범위가 없는 경우처럼 파이프로 보호 중인 공유 리소스에 안전하게 액세스할 수 있는지 확인할 때 사용합니다.

bool FPipe::IsInContext() const;
태그