UE多线程数据传递最佳设计实践及主线程回调实现问询
无手动Tick轮询的UE异步事件评估最佳实践
不需要自己实现双队列轮询逻辑,UE的原生任务调度系统已经封装了线程间任务投递能力,可直接满足需求:
- 第一步先做数据隔离:把需要打分的字段从UObject类型的
Occurrence中拷贝出来,封装为POD结构体,同时用TWeakObjectPtr存储Owner的弱引用(避免UObject被GC后出现野指针),后台线程仅操作这份纯数据副本,完全不碰UObject对象。 - 第二步在后台线程打分完成后,直接用UE提供的主线程任务投递接口把回调抛到游戏线程执行,无需自己维护队列和轮询:
// 后台线程打分逻辑结束后执行 int32 FinalScore = CalculateScore(EvaluateParam); // 直接将回调任务投递到游戏线程队列 AsyncTask(ENamedThreads::GameThread, [WeakOwner = EvaluateParam.WeakOwner, OccurrenceData = EvaluateParam.OccurrenceData, FinalScore]() { // 执行前先校验Owner是否还存活 if (UOwnerType* ValidOwner = WeakOwner.Get()) { ValidOwner->PurposeSelected(OccurrenceData, FinalScore); } });
如果需要更规范的异步任务封装,也可以搭配TFuture的Then接口使用,同样不需要手动轮询任务状态:
// 提交后台打分任务 TFuture<int32> ScoreFuture = Async(EAsyncExecution::Thread, [=]() { return CalculateScore(EvaluateParam); }); // 任务完成后自动执行回调 ScoreFuture.Then([WeakOwner = EvaluateParam.WeakOwner, OccurrenceData = EvaluateParam.OccurrenceData](TFuture<int32> Future) { int32 FinalScore = Future.Get(); // 投递到游戏线程执行业务逻辑 AsyncTask(ENamedThreads::GameThread, [=]() { if (UOwnerType* ValidOwner = WeakOwner.Get()) { ValidOwner->PurposeSelected(OccurrenceData, FinalScore); } }); });
后台线程切回游戏线程的底层逻辑
不存在直接切换线程执行栈的魔法机制,底层本质还是任务队列+固定轮询,只是UE把这部分逻辑封装在了引擎底层,业务层感知不到:
- UE的每个指定命名线程(游戏线程、渲染线程、IO线程等)都维护了一个线程安全的任务队列,
AsyncTask(ENamedThreads::GameThread, 回调)的本质就是把你传入的回调函数打包成任务对象,Push到游戏线程的任务队列中。 - UE游戏线程的主循环每帧都会在固定阶段遍历自己的任务队列,取出所有待执行的任务依次执行,这时候回调的执行上下文就自然处于游戏线程中。
- 你最初想到的双队列方案和引擎底层逻辑是一致的,只是引擎把队列维护、轮询的逻辑做在了内核层,不需要业务层额外写Tick查询,所以会有“无需轮询”的使用体验。
内容的提问来源于stack exchange,提问作者LittleGoblinThatCould
相关产品推荐
相关产品推荐

