中EN
概念页

任务系统

Tasks System

更新于 2026-10-12 约 4 分钟UE5多线程TasksTaskGraph并发

UE::Tasks 是 UE5 的任务级并行库,是对 TaskGraph 的改进,与 TaskGraph 共用同一套调度器和工作线程。它支持启动任务、等待或取结果、声明前置依赖、嵌套任务、管道 (Pipe) 和任务事件。官方建议用依赖关系把工作连成图,而不是阻塞等待;5.5 起用「超额订阅」取代了容易死锁的忙等待。

原理

官方:Tasks System 改进自 UE 原有的作业管理器 TaskGraph,两者使用同一个后端(调度器与工作线程)。主要功能:(1) 提供可调用对象来异步启动任务;(2) 等待任务完成或取得结果;(3) 指定前置任务 (prerequisites);(4) 在任务内部启动嵌套任务,所有嵌套任务完成后父任务才算完成;(5) 构建任务链,即管道 (pipe);(6) 用任务事件 (task event) 做同步和通知。(Tasks Systems in Unreal Engine)这是 多线程渲染与任务系统 中「任务图」思想在 UE 里的实现。

flowchart LR
  A["任务 A:加载数据"] --> C["任务 C:合并结果"]
  B["任务 B:计算"] --> C
  C --> D["任务 D:提交给游戏线程"]
  subgraph P["管道 Pipe:串行访问共享资源"]
    P1["写入 1"] --> P2["写入 2"]
  end

图中 A、B 是 C 的前置任务,C 等两者都完成后才开始;下方的管道中,任务按顺序一个接一个执行,从而无需加锁就能串行访问同一个资源。

用法要点

  • 启动任务时要给一个调试名(用于定位是哪段代码启动的任务)和任务体。可以「发射后不管」,也可以通过优先级影响执行顺序(高优先级先执行)。启动即开始任务的生命周期并分配资源。(Tasks Systems in Unreal Engine)
  • 少用阻塞等待:Wait 和 GetResult() 会阻塞调用线程,直到任务完成;官方建议「通过定义任务之间的依赖关系来构建任务图,并设计基于任务的异步 API」。依赖的主要好处是不阻塞工作线程。(Tasks Systems in Unreal Engine)
  • 管道代替互斥锁:传统做法是对共享资源加锁,线程会被阻塞,资源争用时性能代价很大。管道是一串依次执行(不并发)的任务;官方设计意图是每个共享资源一个管道,对它的所有访问都通过该管道启动的任务进行。(Tasks Systems in Unreal Engine)

坑:忙等待与超额订阅

5.5 起废弃忙等待 (busy-waiting)。以前忙等待会在等待时执行别的任务,但它可能挑到一个反过来依赖当前等待任务的任务,在同一个调用栈上形成无法解开的循环依赖,经常导致死锁;挑到长任务时会卡顿,还容易栈溢出。5.5 改用超额订阅 (oversubscription):等待时唤醒额外的线程,代价是多用一点内存。大多数等待函数已经内置了超额订阅作用域,程序员无需额外处理。这些额外线程称为 standby 线程,会在 Unreal Insights 中显示。(Tasks Systems in Unreal Engine,Insights 见 UE5 性能分析)

与 GEA 的对照(卷 I 第 4 章)

  • 管道 vs 锁:官方说互斥锁会阻塞线程、争用时代价大,建议用管道串行访问共享资源。GEA 给出了底层原因:内核同步原语每次都要内核调用,上下文切换可超过 1000 个时钟周期(线程同步原语),并主张尽量减少争用和锁的使用(基于锁的并发问题)。管道其实就是 GEA 所说「把操作串行化」的一种任务级实现。
  • 忙等待死锁:官方描述的「等待时执行别的任务,却挑到反过来依赖当前任务的任务」正是 GEA 的 Coffman「循环等待」条件。GEA 4.4.7 提到可以用纤程实现协作式的任务系统,并警告纤程里不能调用阻塞的系统调用;UE 5.5 选择的是另一条路——唤醒额外线程(超额订阅)(进程、线程与纤程)。两者都是为了在「等待」时不让工作线程空转或死锁,做法不同,本页不判断优劣。
  1. Tasks Systems in Unreal Engine UE 5.8 文档
  2. Game_Engine_Architecture_4Ed_-_Jason_Gregory.pdf
  3. Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 I 第 4 章 4.4.7、4.6、4.7 节

相关页面

游戏开发 · UE5委托与 Tick 机制Delegates & TickUE 有两种驱动游戏逻辑的方式:事件驱动——委托 (Delegate) 在某事发生时回调绑定的函数;轮询驱动——Tick 每帧(或按最小间隔)调用 Actor/组件的更新函数,并按 Tick 组 (Tick Group) 与物理模拟排序。官方建议能用定时器、委托就不用 Tick,必须 Tick 时用 Tick 组和 Tick 依赖保证顺序,避免“差一帧”问题。游戏开发多线程渲染与任务系统Multithreaded Rendering & Task SystemsCPU 时钟频率约在 2003 年停在 3.4 GHz 附近后,性能来自多核。应用阶段的优化一是让代码与内存访问更缓存友好,二是利用多核:多处理器流水线(时间并行,提高吞吐但增加延迟)、并行处理(空间并行,延迟更低)以及两者结合的任务系统;图形 API 用命令缓冲让多个线程并行录制绘制命令,新一代 API(DirectX 12、Vulkan、Metal)把更多责任交给应用以降低驱动开销。游戏开发 · UE5UE5 渲染架构UE5 Rendering ArchitectureUE5 的渲染器是独立的 Renderer 模块,在渲染线程 (Rendering Thread) 上以「场景代理」镜像游戏线程的组件状态,每帧按固定顺序执行深度预通道、G-buffer 基础通道、光照、雾、半透明和后处理。现代渲染代码用渲染依赖图 (Render Dependency Graph, RDG) 声明通道与资源,由 RDG 自动排屏障、复用瞬态内存、并行录制;最终命令经 RHI 线程翻译成 D3D12/Vulkan 等图形 API 调用。默认走延迟着色 (Deferred Shading),也可切换前向着色 (Forward Shading)。游戏开发 · UE5UE5 性能分析UE5 ProfilingUE5 性能分析的基本流程是:先用 stat unit 判断瓶颈在游戏线程、渲染线程还是 GPU,再用对应的 stat 组、profilegpu/GPU 统计或 Unreal Insights 的 Timing/Memory 等视图向下钻取;渲染特性(RDG、VSM、TSR)各自提供作用域和可视化。测量时要避开会扭曲数据的设置(调试构建、异步计算、可视化模式)。游戏开发进程、线程与纤程Processes, Threads & Fibers并发编程依赖操作系统提供的服务:内核态与用户态、中断、系统调用、抢占式多任务。进程只是为线程提供运行环境(虚拟地址空间与资源),线程才是操作系统的基本执行单位(调用栈 + 寄存器)。线程由内核抢占调度;纤程 (Fiber) 由程序协作调度,适合实现引擎任务系统;用户级线程与协程完全在用户空间切换上下文,更轻量。游戏开发线程同步原语Thread Synchronization Primitives并发编程的中心问题是数据竞争 (Data Race):多个控制流交错地读改写同一共享对象。解决思路是让同一对象上的关键操作具备原子性(看起来瞬间完成,从而被串行化)。最简单可靠的工具是操作系统提供的线程同步原语:互斥锁、临界区、条件变量、信号量、Windows 事件。它们好用但昂贵(需要内核调用,上下文切换可超过 1000 个时钟周期)。游戏开发基于锁的并发问题Problems with Lock-Based Concurrency即使所有共享数据操作都用锁保护好了,基于锁的并发仍有四类问题:死锁 (Deadlock)、活锁 (Livelock)、饥饿 (Starvation)、优先级反转 (Priority Inversion)。哲学家就餐问题集中体现了它们。GEA 由此总结出经验法则:全局排序、事务式算法、尽量减少争用;并反对把所有接口都做成「100% 线程安全」,主张正视并发、少用锁。游戏开发作业系统Job System多核硬件上,游戏循环的工作要分解成可并行执行的子任务。GEA 依次比较「每子系统一个线程」「分散/汇集 (scatter/gather)」和通用的作业系统 (job system),并给出典型作业 API、基于线程池的简单实现及其局限、用协程或纤程让作业可以睡眠、作业计数器、作业同步原语与可视化剖析工具,最后介绍 Naughty Dog 基于纤程的作业系统。(卷 I 8.6 节)游戏开发游戏对象并发更新Concurrent Game Object Updates游戏对象模型要和几乎必然是多线程的底层子系统打交道,所以对象代码必须异步思考:耗时操作用非阻塞请求尽早发起、尽晚取结果,必要时容忍一帧延迟。作业之间的依赖决定了并行度 (DOP),每个依赖都引入同步点,"优化并发设计的秘诀是延迟"。对象模型本身也能并行:Naughty Dog 在把引擎从 PS3 移植到 PS4(《最后生还者 重制版》)时,让每个桶内的对象更新成为作业;桶间访问无需加锁,桶内读取用对象快照,桶内修改尽量避免,或加锁、或放进请求队列、或交给下一桶中的同步作业(GEA4 卷 II 17.7)。

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原