ue 5.8
A首推UE::Tasks::Launch
5.0版本引入,线程来源TaskGraph Worker pool,只能在work线程上运行,返回TTask<T>,并且能Cancel.
深层代码 俩系统都有不同的 FTaskBase,只能命名空间不一样
主要规则
- ❌不要在 Task Worker 线程直接操作 UObject:GC、对象销毁会直接 crash。
- ❌不要主线程 Tick 轮询
IsCompleted()模拟通知,高耗 CPU。 - ❌不要捕获裸 UObject 指针,一律
TWeakObjectPtr。 - ❌不要在 Task 内部调用
Sleep(),会占用工作线程池,挤压其他任务调度。 - ✅想要 "主动通知",不要用 Wait;Wait 是同步等待,不是回调通知。
task 结束 通知问题
| 方案 | 回调运行线程 | 可操作 UObject | 是否阻塞 | 适用场景 |
|---|---|---|---|---|
| 任务体内尾部执行 | Worker | ❌ | 不阻塞 | 纯 C++ 数据计算完成通知 |
| 任务内 Async 切 GameThread | GameThread | ✅ | 不阻塞 | 绝大多数游戏业务、UI、Actor 更新 |
| FPipe 链式任务 | Worker | ❌ | 不阻塞 | 多步串行后台任务流 |
| Wait / GetResult() | 当前线程 | 看调用位置 | ✅阻塞 | 单元测试、加载阶段同步等待,禁止游戏主线程 |
举例
无通知
cpp
FTask MyTask = Launch(UE_SOURCE_LOCATION, []()
{
// 后台耗时计算
int32 Result = HeavyCalc();
// ==========任务完成主动通知(当前仍在工作线程)==========
UE_LOG(LogTemp, Log, TEXT("Task done, result=%d"), Result);
// 注意:这里不能访问UObject!
});
AsyncTask切回Gamethread 最好的方法
cpp
#include "Async/Async.h"
void Example_AsyncNotify(AActor* MyActor)
{
TWeakObjectPtr<AActor> WeakActor(MyActor); // 必须弱指针,防止对象销毁悬空
Launch(UE_SOURCE_LOCATION, [WeakActor]()
{
//=====后台线程耗时工作=====
float ComputeValue = ExpensiveWork();
//=====完成后主动通知,切回GameThread=====
AsyncTask(ENamedThreads::GameThread, [WeakActor, ComputeValue]()
{
if (!WeakActor.IsValid()) return;
// GameThread上下文,可以安全操作Actor、UObject、蓝图委托
UE_LOG(LogTemp, Log, TEXT("回到游戏线程,计算结果=%f"), ComputeValue);
});
});
}
FPipe 任务管道,链式执行(依赖式通知)
FPipe构建任务链:前一个任务结束,自动调度下一个任务;所有任务在同一个 Pipe 串行执行,不会并发争抢资源。 适合:把 "后台工作" 与 "完成后处理" 拆成两个任务,形成完成通知链路。
cpp
FPipe Pipe;
Launch(UE_SOURCE_LOCATION, [](){
UE_LOG(LogTemp, Log, TEXT("Step1: heavy work"));
}, Pipe);
// 前一个任务结束自动跑这个,作为完成回调;全部跑在worker线程
Launch(UE_SOURCE_LOCATION, [](){
UE_LOG(LogTemp, Log, TEXT("Step2: notify, previous task finished"));
}, Pipe);
- FPipe 保证同一管道任务 FIFO 串行执行,前一个完成才跑下一个。
- 续任务依然在 Worker 线程,如果要操作 UObject,内部再套一层
AsyncTask(GameThread)切回去。
封装委托回调(比较笨,不推荐)
cpp
DECLARE_DELEGATE_OneParam(FOnTaskComplete, int32);
void RunWorkWithCallback(FOnTaskComplete OnComplete)
{
Launch(UE_SOURCE_LOCATION, [OnComplete]()
{
int32 Res = HeavyCalc();
AsyncTask(ENamedThreads::GameThread, [OnComplete, Res]()
{
OnComplete.ExecuteIfBound(Res);
});
});
}
靠Wait或者GetResult 阻塞当前线程获得结果(完全不推荐)
B: TGraphTask / FFunctionGraphTask
ue4自带的,引擎底层原生异步系统,牛逼之处是能指定线程来运行
FFunctionGraphTask 属于 UE TaskGraph(任务图系统) ,核心用途:把一个可执行函数 / 回调封装成任务,投递到 TaskGraph 线程池执行,支持立即执行、延迟调度、依赖关系、指定执行线程,大量用于动画、物理、渲染、Slate、AsyncTask、蓝图编译、异步计算。
关键底层机制 & 坑点(高频踩坑)
- 生命周期风险:捕获 UObject 一定要小心
TFunction是异步执行,捕获裸指针 UObject,如果对象中途 GC 销毁,直接野指针崩溃。
- 安全做法:捕获
TWeakObjectPtr<UXXX>,回调内部先 IsValid () 校验。 - ❌禁止:
[MyUObject](){ MyUObject->DoSomething(); }裸捕获 UObject。
- 线程约束
ENamedThreads::GameThread:任务只会在游戏主线程跑,可以安全访问 UObject、蓝图对象。AnyThread / Background:绝对不能直接操作 UObject,UObject 非线程安全。
ESubsequentsMode::FireAndForget设置之后:GetCompletionEvent()返回无效引用,外部不能依赖该任务。适合不需要后续依赖、纯点火即忘的任务,内存开销略小。- 和
UE::Tasks(新 Task 系统)对比
FFunctionGraphTask:老 TaskGraph ,基于FGraphEvent,擅长复杂多任务依赖链,很多引擎内部模块仍大量在用。UE::Tasks::Launch():UE5 新任务库,API 更现代,但底层一部分仍然复用 TaskGraph。
- 不要保存返回的
FFunctionGraphTask*指针! 引擎内部任务完成后自动回收该对象;保存裸指针后续访问是野指针。只能保存FGraphEventRef(智能引用)。 - 调度不等于立刻执行
CreateAndDispatchWhenReady:把任务提交到队列 ;如果前置依赖没完成,任务会挂起,不会马上跑。名字里WhenReady含义:就绪(全部 prerequisites 完成)之后才调度执行。
用法
1、FFunctionGraphTask::CreateAndDispatchWhenReady
可以直接 lamabada
cpp
FFunctionGraphTask::CreateAndDispatchWhenReady(
[]()
{
UE_LOG(LogTemp, Log, TEXT("在后台线程执行耗时计算"));
},
{}, // 无前置依赖
ENamedThreads::AnyBackgroundThreadNormalTask
);
cpp
TArray<FGraphEventRef> Prereqs;
// ...填充Prereqs为前面异步任务的GetCompletionEvent()
FFunctionGraphTask::CreateAndDispatchWhenReady(
[]()
{
check(IsInGameThread());
// 回到游戏线程操作UObject
},
Prereqs,
ENamedThreads::GameThread
);
结束的时候 消息通知
cpp
FFunctionGraphTask::CreateAndDispatchWhenReady(
[]()
{
//===== 异步耗时工作 =====
DoHeavyWork();
//===== 任务结束通知(当前任务线程)=====
UE_LOG(LogTemp, Log, TEXT("任务已经完成"));
// 如果要在GameThread触发蓝图/对象回调
FFunctionGraphTask::CreateAndDispatchWhenReady(
[](){
check(IsInGameThread());
// Broadcast 委托、调用UObject接口
},
{},
ENamedThreads::GameThread
);
},
{},
ENamedThreads::AnyBackgroundThreadNormalTask
);
cpp
TArray<FGraphEventRef> Prereqs;
// ...填充Prereqs为前面异步任务的GetCompletionEvent()
FGraphEventRef Task1= FFunctionGraphTask::CreateAndDispatchWhenReady(
[]()
{
UE_LOG(LogTemp, Log, TEXT("在后台线程执行耗时计算"));
},
{}, // 无前置依赖
ENamedThreads::AnyBackgroundThreadNormalTask
);
Prereqs.Add(Task1);
auto RemoveTrackableTask = FSimpleDelegateGraphTask::FDelegate::CreateRaw(this, &FRemoteSessionARSystemChannel::ReceiveRemoveTrackable_GameThread, UniqueId);
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(RemoveTrackableTask, GET_STATID(STAT_ARSystemChannel_Receive), Prereqs, ENamedThreads::GameThread);
2、FSimpleDelegateGraphTask::CreateAndDispatchWhenReady
3、FDelegateGraphTask::CreateAndDispatchWhenReady
| 项目 | FFunctionGraphTask | FDelegateGraphTask | FSimpleDelegateGraphTask |
|---|---|---|---|
| 入参类型 | TUniqueFunction<...> |
FGraphTaskDelegate(双参委托) |
FSimpleDelegate(零参委托) |
| 回调签名 | void() 或 void(ENamedThreads::Type, FGraphEventRef&) |
void(ENamedThreads::Type, const FGraphEventRef&) |
void(),完全无参数 |
| Lambda 捕获局部变量 | ✅支持,move 捕获 | ❌委托本身不能存闭包;只能 BindUObject/BindRaw | ❌不支持 |
| 回调拿到任务 / 线程信息 | ✅重载版本支持 | ✅原生支持,回调直接带参数 | ❌拿不到 |
| UObject 安全检测 | 无,靠 lambda 内部 TWeakObjectPtr | 委托自带 UObject 句柄有效性检测 | 委托自带 UObject 句柄有效性检测 |
| 底层实现 | TGraphTask<TFunctionGraphTaskImpl> |
TGraphTask<FDelegateGraphTask> |
TGraphTask<FDelegateGraphTask>(上层封装) |
| 典型使用场景 | 现代 C++,lambda,需要捕获栈变量;渲染 / 业务 TaskGraph 任务 | 已有双参委托;回调内部需要知道当前线程、任务 Event | 老引擎代码,已有 FSimpleDelegate,只做简单回调 |
C: Async\AsyncTask
基于 GraphTask的高级封装 也非常常用
Async 是有 Future 和 Promis的 是可以不同线程 通讯的 很方便 传递值
cpp
AsyncTask(ENamedThreads::AnyThread, [this, NormalAssetData, AssetMetaData, LocationOffset]() {
AsyncNormalImportCache(NormalAssetData, AssetMetaData, LocationOffset);
});
cpp
TFuture<void> FinalizeTask = Async(EAsyncExecution::Thread, [&IoStoreWriterContext]()
{
IoStoreWriterContext->FinalizeWrites();
});
todo 将来需要讲解的 太多了
FAsyncTask/FAutoDeleteAsyncTask
ParallelFor
FRunnableThread
IAsyncReadFileHandle
仅仅在GameThread的 异步类型
UAsyncAction
Timer / LatentAction
GameplayTask