简单对照
| 写法 | 异步 | 多线程计算 |
|---|---|---|
| 普通函数调用 | 否 | 否 |
AsyncTask(GameThread, ...) |
是 | 否,仍在 Game Thread |
Async(ThreadPool, ...) |
是 | 是,通常由工作线程执行 |
ParallelFor(...) |
通常用于并行 | 是 |
Future.Get() |
会等待 | 不一定 |
AsyncTask:一次性短任务
适合:
- 任务只执行一次
- 任务执行时间较短
- 不需要自己管理线程生命周期
- 任务完成后执行一个回调
Task Graph / UE Tasks:多个任务有依赖关系
适合:
- 有多个任务
- 任务之间存在先后依赖
- 某些任务可以同时执行
- 需要等待多个任务全部完成
- 需要组织复杂的任务流程
例如:
任务 A:计算碎片面积
↓
任务 B:汇总总面积
任务 C:生成 Mesh
任务 D:生成碰撞
↓
任务 E:提交结果
FRunnable:长期运行的专用线程
适合:
- 线程需要长期存在
- 线程不断等待新任务
- 有自己的运行循环
- 需要持续处理网络、设备、文件流等数据
- 任务不是执行一次就结束
普通数据
TArray<FVector> Vertices;
你可以规定:
Worker Thread 独占写入
Game Thread 等完成后读取
不会有 UE 对象系统参与。
UE 组件
UDynamicMeshComponent* Component;
它不是一块独立内存,还连接着:
Actor
World
UObject 生命周期
渲染状态
物理状态
组件注册表
Worker Thread 创建它时,不只是"分配一块内存",而是在修改整个 UE 场景。
具体会发生什么
你写:
UDynamicMeshComponent* Component =
NewObject<UDynamicMeshComponent>(Owner);
Component->RegisterComponent();
表面上只有两行,实际上会涉及:
创建 UObject
把组件挂到 Owner
把组件注册到 World
通知渲染系统创建渲染状态
通知物理系统创建碰撞状态
更新 Actor 的组件关系
与此同时,Game Thread 可能正在:
遍历 Actor
更新组件
销毁对象
切换关卡
处理碰撞
执行垃圾回收
如果两个线程同时修改这些内部状态,就会出现:
线程 A:正在添加组件
线程 B:正在遍历组件
结果可能是:
- 数据竞争
- 数组被同时修改
- 随机崩溃
- 组件注册不完整
- 渲染状态丢失
- Chaos 碰撞状态异常
不能在 Worker Thread 直接做的事情总结
通常要回到 Game Thread 的包括:
Actor 创建、销毁、修改
Component 创建、注册、销毁
UObject 生命周期操作
World 和 Level 修改
实时射线检测
材质、渲染状态修改
Chaos 物理状态修改
输入、计时器、Timeline
Gameplay Framework
网络复制和 RPC
AI、导航、行为树
动画组件
音频组件
编辑器资源操作
委托绑定和广播
但是上面的内容可以只读不修改
Worker Thread 适合做的事情
适合放后台的通常是:
纯数学计算
字符串解析
文件数据解析
JSON 解析
网格顶点计算
三角形计算
二维 polygon 生成
碰撞几何数据生成
纹理像素数据处理
路径算法的纯数据部分
批量数组计算
排序、过滤、统计