Skip to content

UE5 任务系统 TaskSystem VS TaskGraph ​

TaskGraph系统 ​

TaskGraph简介 ​

TaskGraph是虚幻引擎的底层任务系统,提供了强大的任务依赖管理和执行控制能力。它是引擎内部许多系统的基础,如渲染、物理模拟等。

TaskGraph基本用法 ​

1. 任务类型 ​

TaskGraph支持几种主要的任务类型:

cpp
// 1. 普通任务
FGraphTask::CreateTask().ConstructAndDispatchWhenReady([]() {
    // 任务代码
});

// 2. 带名称的任务
FGraphTask::CreateTask(nullptr, ENamedThreads::AnyThread)
    .ConstructAndDispatchWhenReady([]() {
        // 任务代码
    }, TStatId());

// 3. 带依赖的任务
FGraphEventRef Event = FGraphTask::CreateTask(Prerequisites, ENamedThreads::AnyThread)
    .ConstructAndDispatchWhenReady([]() {
        // 任务代码
    }, TStatId());

2. 线程类型 ​

TaskGraph提供了详细的线程控制:

cpp
enum ENamedThreads::Type
{
    AnyThread,           // 任意工作线程
    GameThread,          // 游戏线程
    RenderThread,        // 渲染线程
    RHIThread,          // RHI线程
    // ... 其他专用线程
};

3. 任务依赖 ​

cpp
// 创建前置任务
FGraphEventRef Prerequisite = FGraphTask::CreateTask().ConstructAndDispatchWhenReady([]() {
    // 前置任务代码
});

// 创建依赖任务数组
FGraphEventArray Prerequisites;
Prerequisites.Add(Prerequisite);

// 创建依赖于前置任务的新任务
FGraphEventRef DependentTask = FGraphTask::CreateTask(&Prerequisites, ENamedThreads::AnyThread)
    .ConstructAndDispatchWhenReady([]() {
        // 依赖任务代码
    }, TStatId());

TaskGraph高级功能 ​

1. 任务优先级 ​

cpp
// 高优先级任务
FGraphTask::CreateTask(nullptr, ENamedThreads::AnyHiPriThreadHiPriTask)
    .ConstructAndDispatchWhenReady([]() {
        // 高优先级任务代码
    });

// 普通优先级任务
FGraphTask::CreateTask(nullptr, ENamedThreads::AnyThread)
    .ConstructAndDispatchWhenReady([]() {
        // 普通优先级任务代码
    });

2. 等待任务完成 ​

cpp
// 等待单个任务
FTaskGraphInterface::Get().WaitUntilTaskCompletes(TaskEvent);

// 等待多个任务
FTaskGraphInterface::Get().WaitUntilTasksComplete(TaskEvents);

3. 条件执行 ​

cpp
// 条件任务创建
if (bShouldCreateTask)
{
    FGraphTask::CreateTask().ConstructAndDispatchWhenReady([]() {
        // 条件任务代码
    });
}

TaskGraph使用场景 ​

TaskGraph特别适合以下场景:

  1. 引擎核心系统

    • 渲染管线
    • 物理模拟
    • 动画系统
  2. 复杂的任务依赖关系

    • 多阶段处理
    • 复杂的并行计算
  3. 特定线程要求

    • 需要在特定线程执行的任务
    • 跨线程同步操作

TaskSystem系统 ​

TaskSystem简介 ​

UE5引入了一个新的任务系统(TaskSystem),它是对底层TaskGraph系统的高层封装。这个新系统提供了更简单、更直观的API,使得异步任务的管理变得更加容易。

任务优先级 ​

任务系统支持两种优先级系统:

  1. 基本优先级(ETaskPriority):
cpp
enum class ETaskPriority 
{
    Normal,  // 普通优先级
    High,    // 高优先级
}
  1. 扩展优先级(EExtendedTaskPriority):
cpp
enum class EExtendedTaskPriority
{
    None,      // 无特殊优先级
    Inline,    // 内联执行
    TaskEvent, // 任务事件优先级
    // 游戏线程相关
    GameThreadNormalPri,
    GameThreadHiPri,
    // 渲染线程相关
    RenderThreadNormalPri,
    RenderThreadHiPri,
    // RHI线程相关
    RHIThreadNormalPri,
    RHIThreadHiPri,
}

任务状态 ​

任务可能处于以下几种状态:

  • 等待执行
  • 正在执行
  • 已完成
  • 已取消

TaskSystem基本用法 ​

1. 创建简单任务 ​

cpp
// 最简单的任务创建方式
auto Task = UE::Tasks::Launch(
    UE_SOURCE_LOCATION,  // 用于调试的位置信息
    []{ 
        UE_LOG(LogTemp, Log, TEXT("Hello from Task!")); 
    }
);

// 等待任务完成
Task.Wait();

2. 带返回值的任务 ​

cpp
// 创建返回整数的任务
auto Task = UE::Tasks::Launch(
    UE_SOURCE_LOCATION,
    []() -> int { 
        // 执行一些计算
        return 42; 
    }
);

// 获取任务结果(会等待任务完成)
int result = Task.GetResult();

3. 设置任务优先级 ​

cpp
auto Task = UE::Tasks::Launch(
    UE_SOURCE_LOCATION,
    []{ /* 任务代码 */ },
    ETaskPriority::High,  // 设置高优先级
    EExtendedTaskPriority::None  // 无特殊优先级
);

4. 任务依赖关系 ​

cpp
// 创建第一个任务
auto Task1 = UE::Tasks::Launch(
    UE_SOURCE_LOCATION,
    []{ /* 任务1代码 */ }
);

// 创建依赖于Task1的任务
auto Task2 = UE::Tasks::Launch(
    UE_SOURCE_LOCATION,
    []{ /* 任务2代码 */ },
    UE::Tasks::Prerequisites(Task1)  // Task2会等待Task1完成后才开始执行
);

5. 更多细节 ​

虽然任何可调用对象也可以使用,但 lambda 函数通常用作任务体。

cpp
void Func() {}
        Launch(UE_SOURCE_LOCATION, &Func);

        struct FFunctor
        {
            void operator()() {}
        };
        Launch(UE_SOURCE_LOCATION, FFunctor{});

FTask是实际任务的句柄,类似于智能指针。它使用引用计数来管理其生命周期。启动任务会启动其生命周期并分配所需的资源。要释放持有的引用,您可以使用以下命令“重置”任务句柄:

cpp
FTask Task = Launch(UE_SOURCE_LOCATION, []{});
Task = {}; // release the reference

TaskSystem高级功能 ​

1. 任务事件 ​

任务事件用于同步多个任务:

cpp
// 创建任务事件
FTaskEvent Event(TEXT("MyEvent"));

// 添加事件的前置条件(可选)
Event.AddPrerequisites(SomePrerequisites);

// 在某个时刻触发事件
Event.Trigger();

// 使用事件作为任务的前置条件
auto Task = UE::Tasks::Launch(
    UE_SOURCE_LOCATION,
    []{ /* 任务代码 */ },
    UE::Tasks::Prerequisites(Event)
);

2. 任务取消 ​

cpp
// 创建取消令牌
FCancellationToken Token;

// 创建可取消的任务
auto Task = UE::Tasks::Launch(
    UE_SOURCE_LOCATION,
    [&Token]{ 
        // 定期检查是否被取消
        if (Token.IsCanceled()) {
            return;
        }
        // ... 继续执行
    }
);

// 在需要时取消任务
Token.Cancel();

3. 等待多个任务 ​

cpp
// 创建任务数组
TArray<FTask> Tasks;

// 添加多个任务
Tasks.Add(UE::Tasks::Launch(/*...*/));
Tasks.Add(UE::Tasks::Launch(/*...*/));

// 等待所有任务完成
UE::Tasks::Wait(Tasks);

// 等待任意一个任务完成
int32 CompletedTaskIndex = UE::Tasks::WaitAny(Tasks);

TaskSystem使用场景 ​

TaskSystem特别适合以下场景:

  1. 一般的游戏逻辑开发
  2. 需要快速实现的功能
  3. 简单的异步操作
  4. 需要良好错误处理
  5. 团队开发合作项目

引擎内部使用示例 ​

1. 表格合并任务 (TableMergeTask) ​

这是一个来自引擎Trace系统的实际例子,展示了TaskSystem和TaskGraph如何协同工作:

\Engine\Source\Developer\TraceServices\Private\Model\TableMergeTask.cpp

cpp
// 在TraceServices命名空间中
void FTableMergeService::MergeTables(
    const TSharedPtr<IUntypedTable>& TableA, 
    const TSharedPtr<IUntypedTable>& TableB, 
    TableDiffCallback InCallback)
{
    // 使用TaskSystem启动表格合并任务
    UE::Tasks::Launch(
        UE_SOURCE_LOCATION, 
        FTableMergeTask(TableA, TableB, InCallback)
    );
}

这个例子展示了几个重要特点:

  1. TaskSystem的简单性:使用Launch函数一行代码就能启动复杂的异步任务

  2. 自定义任务对象:FTableMergeTask是一个函数对象,包含:

    • 输入数据(TableA和TableB)
    • 回调函数(用于处理结果)
    • 实现了operator()来执行实际的合并操作
  3. TaskSystem与TaskGraph的集成:任务完成后,使用TaskGraph将结果转发到游戏线程:

cpp
// 使用TaskGraph在GameThread上执行回调
FFunctionGraphTask::CreateAndDispatchWhenReady(
    [Callback = this->Callback, Params]()
    {
        Callback(Params);
    },
    TStatId(),
    nullptr,
    ENamedThreads::GameThread
);

这个示例很好地展示了:

  • 如何使用TaskSystem处理耗时的后台操作
  • 如何在需要特定线程上下文时(如UI更新)切换到TaskGraph
  • TaskSystem和TaskGraph如何在实际场景中互补使用:
    • TaskSystem处理高层的任务抽象和管理
    • TaskGraph处理底层的线程特定操作

系统对比 ​

API设计 ​

  • TaskGraph:

    • 更底层的API
    • 更细粒度的控制
    • 需要更多的样板代码
    • 直接与引擎线程系统集成
  • TaskSystem:

    • 更高层的抽象
    • 更简洁的API
    • 更少的样板代码
    • 自动处理大多数常见场景

功能特性 ​

  • TaskGraph:

    • 完全控制任务调度
    • 细粒度的线程控制
    • 直接的性能控制
    • 适合底层系统开发
  • TaskSystem:

    • 内置任务取消支持
    • 简化的依赖管理
    • 类型安全的任务返回值
    • 更好的错误处理

性能特点 ​

  • TaskGraph:

    • 更少的抽象开销
    • 更直接的内存控制
    • 可以实现更优的性能
    • 需要更多的专业知识
  • TaskSystem:

    • 略微的抽象开销
    • 自动的内存管理
    • 足够好的性能
    • 更容易避免常见错误

选择建议 ​

使用TaskGraph的场景 ​

  1. 开发引擎级别的功能
  2. 需要精确控制任务调度
  3. 对性能要求极高的场景
  4. 需要深度集成到引擎系统
  5. 复杂的线程交互场景

使用TaskSystem的场景 ​

  1. 一般的游戏逻辑开发
  2. 需要快速实现的功能
  3. 简单的异步操作
  4. 需要良好错误处理
  5. 团队开发合作项目

最佳实践建议 ​

  1. 默认选择TaskSystem
  2. 在遇到性能瓶颈时考虑TaskGraph
  3. 在需要特殊线程控制时使用TaskGraph
  4. 可以在同一项目中混合使用两种系统
  5. 根据团队经验水平选择合适的系统