本技能面向虚幻引擎(Unreal Engine)C++ 开发,主题是“定时器与异步工作”,即如何在游戏中正确地安排延迟、周期性回调与后台计算。资料指出,大多数“稍后执行 / 每 N 秒执行”的需求应交给定时器而不是 Tick,CPU 密集型工作应移出游戏线程,再把结果送回游戏线程应用。
一、定时器(FTimerManager) FTimerManager 管理某个 UWorld 的全部游戏定时器。Actor 通过 GetWorldTimerManager() 获取,非 Actor 代码用 GetWorld()->GetTimerManager() 或 UGameInstance 上的全局实例。资料给出典型用法:在头文件声明 FTimerHandle 成员,在 BeginPlay 中调用 SetTimer 设置每 0.5 秒循环的定时器或 3 秒的一次性定时器;也支持 lambda 变体,并可让首次延迟与重复间隔不同(FirstDelay)。把 FTimerHandle 保存为成员,便于后续取消或查询;对已有效的句柄再次调用 SetTimer 会取消旧定时器并重新开始。
SetTimerForNextTick 用于把工作推迟一帧,例如等待另一个 Actor 完成生成后再继续初始化;它不返回句柄,无法取消。查询与取消接口包括 IsTimerActive、GetTimerRemaining、GetTimerElapsed、PauseTimer、UnPauseTimer、ClearTimer(停止并使句柄失效)、ClearAllTimersForObject。向 SetTimer 传入小于等于 0 的速率等同于 ClearTimer。
资料强制要求:在 EndPlay 中清理所有循环定时器。循环定时器若比其绑定对象存活更久,会调用悬空指针;EndPlay 覆盖销毁、关卡卸载、PIE 结束等所有退出原因。
定时器按世界时间推进,因此自动遵循 WorldSettings 的时间膨胀、暂停(SetPause)与慢动作;即使累计增量超过间隔,每个游戏帧最多触发一次(受 FTimerData 的 bMaxOncePerFrame 影响)。FTimerManager 非线程安全,绝不能在后台线程设置或清除定时器。
二、游戏线程之外的异步工作
- Async / AsyncTask:用 Async(EAsyncExecution::ThreadPool, lambda) 在线程池执行重活,再用 AsyncTask(ENamedThreads::GameThread, ...) 把结果切回游戏线程。工作线程中不得访问 UObject/Actor/组件;Async 返回 TFuture<T> 供轮询或等待。短视频繁工作优先 ThreadPool,长时间工作用 EAsyncExecution::Thread 以免阻塞线程池。跨线程捕获 UObject 时应使用 TWeakObjectPtr,并在回到游戏线程后用 .Get() 重新验证,不能捕获裸 UObject* 或 AActor*。
- FNonAbandonableTask / FAutoDeleteAsyncTask:用于可复用、结构化的后台任务;FAutoDeleteAsyncTask 在完成后自删除,需要等待完成或取结果时改用 FAsyncTask<T>(提供 EnsureCompletion() 与 IsDone())。
- UE Tasks System(UE 5.1+,新代码首选):头文件 Tasks/Task.h,命名空间 UE::Tasks;Launch 可发后不管或返回 TTask 以取结果;任务可声明依赖(B 在 A 完成后运行);FPipe 提供对共享资源的顺序非并发访问。新代码若需 DAG 式依赖或串行访问共享资源,优先用它而非裸 Async/AsyncTask。
- FRunnable / FRunnableThread:用于音频流、网络 I/O、模拟循环等需要独占操作系统线程的长期服务,需实现 Init、Run、Stop、Exit,并由 Actor 在 BeginPlay 创建、EndPlay 中 Kill 并销毁。
- FTSTicker:为没有 UActorComponent 的子系统提供周期回调,取代 UE5 中已移除的 FTicker。AddTicker 返回句柄,回调返回 true 继续、false 表示一次性;RemoveTicker 注销。FTSTickerObjectBase 是便捷基类,子类重写 Tick(float) 即可。
三、线程安全规则(关键) 不得在游戏线程之外读写 UObject/AActor/UActorComponent 状态,包括 GetWorld()、生成、委托广播与 GC 追踪指针。跨线程 lambda 只捕获普通数据的副本(结构体、int32、float);捕获 UObject 用 TWeakObjectPtr<T> 并在游戏线程验证。线程间共享的可变非 UObject 状态用 FCriticalSection / FScopeLock 保护。FTimerManager 仅限游戏线程;FTSTicker::AddTicker 线程安全(回调在游戏线程触发),RemoveTicker 会阻塞至进行中的回调结束。
四、机制选择速查 固定节奏的延迟或重复用 FTimerManager::SetTimer;推迟恰好一帧用 SetTimerForNextTick;逐帧平滑插值用 Tick(按需启用);短后台工作、发后不管用 Async(ThreadPool);需结果或依赖的后台工作用 UE::Tasks::Launch;可复用后台任务类用 FNonAbandonableTask + FAutoDeleteAsyncTask;长期专用 OS 线程用 FRunnable + FRunnableThread;非 Actor 周期回调用 FTSTicker。
五、蓝图潜伏动作 蓝图 Delay 节点与潜伏 K2 函数由 FPendingLatentAction 支撑(注册在 UWorld::GetLatentActionManager())。C++ 中 Actor 逻辑优先用定时器;若需向蓝图暴露带白色执行引脚的“异步节点”,应继承 UBlueprintAsyncActionBase,而非直接实现 FPendingLatentAction。
六、常见陷阱 丢失 FTimerHandle 的循环定时器无法取消;EndPlay 不清理会向已释放内存回调;跨线程捕获裸 UObject* 可能被 GC 回收;速率小于等于 0 会被静默当作 ClearTimer;SetTimerForNextTick 无句柄不可取消,若对象可能在下帧前销毁则不应调用;在游戏线程阻塞等待 future(TFuture::Get() 或 FTask::Wait())会卡住渲染,只应在工作线程或关卡加载等已知安全点阻塞;UE5 中须用 FTSTicker 而非已移除的 FTicker。
七、版本说明 UE5 中 FTSTicker 取代 FTicker,UE4 时代使用 FTicker::GetCoreTicker() 的代码需迁移;UE Tasks System 自 UE 5.0 引入,新代码优先于直接使用 TGraphTask;UE::Tasks 的忙等待在 UE 5.5 弃用,改为超额订阅(备用线程),不要调用已移除的忙等待 API。
八、参考来源 资料列出 UE 5.8 引擎源码位置(如 TimerHandle.h、TimerManager.h、Async/Async.h、AsyncWork.h、TaskGraphInterfaces.h、Future.h、Tasks/Task.h、Tasks/Pipe.h、HAL/Runnable.h、HAL/RunnableThread.h、Containers/Ticker.h、HAL/CriticalSection.h、Misc/ScopeLock.h)及官方文档链接(Gameplay Timers、Tasks System),并附带四篇深入参考:timer-manager.md、async-and-tasks.md、threads-and-runnables.md、tickers-and-latent.md。