ue-async-threading
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUE Async and Threading
UE异步与线程处理
You are an expert in Unreal Engine's threading model, async task systems, and concurrent programming patterns.
你是Unreal Engine线程模型、异步任务系统及并发编程模式方面的专家。
Context Check
上下文检查
Read before proceeding. Engine version matters: is the modern preferred API (UE 5.0+), while and TaskGraph remain fully supported. Determine: What work needs to be offloaded? Is UObject access required? What latency/throughput tradeoff is acceptable?
.agents/ue-project-context.mdUE::Tasks::LaunchFAsyncTask在开始操作前,请阅读。引擎版本很重要:是现代推荐使用的API(UE 5.0+),而和TaskGraph仍完全受支持。需要确定:哪些工作需要卸载?是否需要访问UObject?可接受的延迟/吞吐量权衡是什么?
.agents/ue-project-context.mdUE::Tasks::LaunchFAsyncTaskInformation Gathering
信息收集
Ask the user if unclear:
- Offload type — CPU-bound computation, I/O wait, or periodic background work?
- UObject interaction — Does the background work need to read/write UObject state?
- Lifetime — One-shot task, recurring work, or long-lived thread?
- Result delivery — Fire-and-forget, or does the game thread need results back?
如果信息不明确,请询问用户:
- 卸载类型 — 是CPU密集型计算、I/O等待,还是周期性后台工作?
- UObject交互 — 后台工作是否需要读写UObject状态?
- 生命周期 — 是一次性任务、重复工作,还是长期运行的线程?
- 结果交付 — 是即发即弃,还是需要将结果返回至游戏线程?
UE Threading Model
UE线程模型
UE runs several named threads plus a scalable worker pool. Understanding which thread owns what prevents the most common threading bugs.
Named threads:
- Game Thread — All UObject access, Blueprint execution, gameplay logic. Check with .
IsInGameThread() - Render Thread — Render commands, scene proxy updates. .
IsInRenderingThread() - RHI Thread — GPU command submission (platform-dependent).
- Worker Threads — Unnamed pool threads for task dispatch. Count scales with CPU cores.
The golden rule: UObjects are game-thread-only. No UPROPERTY reads, no UFUNCTION calls, no , no spawning from background threads. Violating this causes intermittent crashes that depend on GC timing and are extremely difficult to diagnose.
GetWorld()UE运行多个命名线程以及一个可扩展的工作线程池。了解哪个线程拥有哪些资源可以避免最常见的线程错误。
命名线程:
- Game Thread — 所有UObject访问、蓝图执行、游戏玩法逻辑都在此线程进行。可通过检查当前是否处于该线程。
IsInGameThread() - Render Thread — 渲染命令、场景代理更新。可通过检查。
IsInRenderingThread() - RHI Thread — GPU命令提交(取决于平台)。
- Worker Threads — 用于任务调度的无名池线程。线程数量随CPU核心数扩展。
黄金法则: UObject仅可在游戏线程中访问。禁止在后台线程中读取UPROPERTY、调用UFUNCTION、使用或生成对象。违反此规则会导致间歇性崩溃,这类崩溃依赖GC时机,极难诊断。
GetWorld()Pattern Selection Guide
模式选择指南
Choose the simplest API that fits your needs.
| Pattern | Best For | Lifetime | Result? |
|---|---|---|---|
| Dispatch to game thread from background | One-shot | No |
| General async work (preferred, UE5+) | One-shot | |
| Flexible dispatch with | One-shot | |
| Reusable pooled work units | Reusable | Via |
| Fire-and-forget pooled work | One-shot | No |
| Complex dependency graphs | One-shot | |
| Data-parallel loops | Blocking | No |
| Long-lived dedicated threads | Persistent | Manual |
选择最符合需求的最简API。
| 模式 | 最佳适用场景 | 生命周期 | 是否返回结果 |
|---|---|---|---|
| 从后台线程向游戏线程调度任务 | 一次性 | 否 |
| 通用异步工作(推荐,UE5+) | 一次性 | |
| 带 | 一次性 | |
| 可复用的池化工作单元 | 可复用 | 通过 |
| 即发即弃的池化工作 | 一次性 | 否 |
| 复杂依赖图 | 一次性 | |
| 数据并行循环 | 阻塞式 | 否 |
| 长期运行的专用线程 | 持久化 | 手动处理 |
FRunnable and FRunnableThread
FRunnable与FRunnableThread
Use only when you need a dedicated, long-lived thread -- a socket listener, a file watcher, or a continuous processing loop. For one-shot work, prefer or .
FRunnableUE::Tasks::LaunchFAsyncTaskLifecycle: (new thread) -> (new thread) -> (new thread, after Run returns). is called externally to request shutdown.
Init()Run()Exit()Stop()FRunnableThread::Create signature: .
static FRunnableThread* Create(FRunnable*, const TCHAR* ThreadName, uint32 StackSize = 0, EThreadPriority = TPri_Normal, uint64 AffinityMask, EThreadCreateFlags)Key points: signals the thread -- it does not block. calls then waits for completion. Always the after . Use in loop, set it in .
Stop()Kill(true)Stop()deleteFRunnableThread*Killstd::atomic<bool> bShouldStopRun()Stop()See for a complete subclass template with proper shutdown.
references/threading-patterns.mdFRunnable仅当你需要专用的长期运行线程(如套接字监听器、文件监视器或持续处理循环)时,才使用。对于一次性工作,优先选择或。
FRunnableUE::Tasks::LaunchFAsyncTask生命周期: (新线程中执行)-> (新线程中执行)-> (新线程中执行,Run返回后触发)。由外部调用以请求关闭。
Init()Run()Exit()Stop()FRunnableThread::Create签名:。
static FRunnableThread* Create(FRunnable*, const TCHAR* ThreadName, uint32 StackSize = 0, EThreadPriority = TPri_Normal, uint64 AffinityMask, EThreadCreateFlags)关键点: 仅向线程发送信号——不会阻塞。会调用然后等待完成。后务必对应的。在循环中使用,并在中设置其值。
Stop()Kill(true)Stop()KilldeleteFRunnableThread*Run()std::atomic<bool> bShouldStopStop()如需完整的子类模板及正确的关闭逻辑,请查看。
FRunnablereferences/threading-patterns.mdFAsyncTask and FAutoDeleteAsyncTask
FAsyncTask与FAutoDeleteAsyncTask
For reusable work units on the engine thread pool (). Subclass and implement + .
GThreadPoolFNonAbandonableTaskDoWork()GetStatId()cpp
class FMyComputeTask : public FNonAbandonableTask
{
friend class FAsyncTask<FMyComputeTask>;
int32 Result = 0;
TArray<int32> InputData;
FMyComputeTask(TArray<int32> InData) : InputData(MoveTemp(InData)) {}
void DoWork()
{
for (int32 Val : InputData) { Result += Val; }
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FMyComputeTask, STATGROUP_ThreadPoolAsyncTasks);
}
};Usage:
cpp
// Reusable — you manage lifetime
auto* Task = new FAsyncTask<FMyComputeTask>(MoveTemp(Data));
Task->StartBackgroundTask(); // dispatches to GThreadPool
Task->EnsureCompletion(); // blocks or runs inline if not started
int32 R = Task->GetTask().Result;
delete Task;
// Fire-and-forget — auto-deletes on completion
(new FAutoDeleteAsyncTask<FMyComputeTask>(MoveTemp(Data)))->StartBackgroundTask();IsWorkDone()Cancel()StartSynchronousTask()适用于引擎线程池()上的可复用工作单元。继承并实现 + 。
GThreadPoolFNonAbandonableTaskDoWork()GetStatId()cpp
class FMyComputeTask : public FNonAbandonableTask
{
friend class FAsyncTask<FMyComputeTask>;
int32 Result = 0;
TArray<int32> InputData;
FMyComputeTask(TArray<int32> InData) : InputData(MoveTemp(InData)) {}
void DoWork()
{
for (int32 Val : InputData) { Result += Val; }
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FMyComputeTask, STATGROUP_ThreadPoolAsyncTasks);
}
};使用方式:
cpp
// 可复用 — 由你管理生命周期
auto* Task = new FAsyncTask<FMyComputeTask>(MoveTemp(Data));
Task->StartBackgroundTask(); // 调度至GThreadPool
Task->EnsureCompletion(); // 阻塞等待,若未启动则在当前线程内联执行
int32 R = Task->GetTask().Result;
delete Task;
// 即发即弃 — 完成后自动销毁
(new FAutoDeleteAsyncTask<FMyComputeTask>(MoveTemp(Data)))->StartBackgroundTask();IsWorkDone()Cancel()StartSynchronousTask()TaskGraph
TaskGraph
For work with complex dependency chains. Each task declares prerequisites; the scheduler handles ordering.
cpp
class FMyGraphTask
{
public:
FMyGraphTask(int32 InValue) : Value(InValue) {}
static ESubsequentsMode::Type GetSubsequentsMode()
{ return ESubsequentsMode::TrackSubsequents; }
ENamedThreads::Type GetDesiredThread()
{ return ENamedThreads::AnyThread; }
TStatId GetStatId() const
{ RETURN_QUICK_DECLARE_CYCLE_STAT(FMyGraphTask, STATGROUP_TaskGraphTasks); }
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{ /* work here */ }
private:
int32 Value;
};Dispatching with prerequisites:
cpp
FGraphEventArray Prerequisites; // TArray<FGraphEventRef, TInlineAllocator<4>>
Prerequisites.Add(SomePriorEvent);
FGraphEventRef TaskEvent = TGraphTask<FMyGraphTask>::CreateTask(&Prerequisites)
.ConstructAndDispatchWhenReady(42); // args forwarded to constructor
FTaskGraphInterface::Get().WaitUntilTaskCompletes(TaskEvent, ENamedThreads::GameThread);Quick dispatch (no custom class needed):
cpp
AsyncTask(ENamedThreads::GameThread, [this]()
{
MyActor->UpdateHealth(NewValue); // safe — runs on game thread
});适用于复杂依赖链的工作。每个任务声明前置条件,调度器会处理执行顺序。
cpp
class FMyGraphTask
{
public:
FMyGraphTask(int32 InValue) : Value(InValue) {}
static ESubsequentsMode::Type GetSubsequentsMode()
{ return ESubsequentsMode::TrackSubsequents; }
ENamedThreads::Type GetDesiredThread()
{ return ENamedThreads::AnyThread; }
TStatId GetStatId() const
{ RETURN_QUICK_DECLARE_CYCLE_STAT(FMyGraphTask, STATGROUP_TaskGraphTasks); }
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{ /* 在此处执行工作 */ }
private:
int32 Value;
};带前置条件的调度:
cpp
FGraphEventArray Prerequisites; // TArray<FGraphEventRef, TInlineAllocator<4>>
Prerequisites.Add(SomePriorEvent);
FGraphEventRef TaskEvent = TGraphTask<FMyGraphTask>::CreateTask(&Prerequisites)
.ConstructAndDispatchWhenReady(42); // 参数会转发至构造函数
FTaskGraphInterface::Get().WaitUntilTaskCompletes(TaskEvent, ENamedThreads::GameThread);快速调度(无需自定义类):
cpp
AsyncTask(ENamedThreads::GameThread, [this]()
{
MyActor->UpdateHealth(NewValue); // 安全 — 在游戏线程执行
});UE::Tasks::Launch (Modern Preferred API)
UE::Tasks::Launch(现代推荐API)
Recommended for new code (UE 5.0+). Simpler syntax than TaskGraph, automatic thread pool dispatch, built-in chaining.
cpp
#include "Tasks/Task.h"
UE::Tasks::TTask<int32> Task = UE::Tasks::Launch(
UE_SOURCE_LOCATION,
[]() { return ExpensiveComputation(); }
);
int32 Result = Task.GetResult(); // blocks until complete
// With prerequisites
UE::Tasks::TTask<FVector> TaskA = UE::Tasks::Launch(UE_SOURCE_LOCATION,
[]() { return ComputePosition(); });
UE::Tasks::TTask<void> TaskB = UE::Tasks::Launch(UE_SOURCE_LOCATION,
[&TaskA]() { ProcessPosition(TaskA.GetResult()); },
UE::Tasks::Prerequisites(TaskA)
);TTask<T> API: blocks and returns result. non-blocking. / for timed blocking. runs inline if not yet started (work stealing).
GetResult()IsCompleted()Wait()Wait(FTimespan)TryRetractAndExecute()FTaskEvent for manual synchronization -- call to unblock dependent tasks.
Trigger()推荐用于新代码(UE 5.0+)。语法比TaskGraph更简洁,自动调度至线程池,内置任务链支持。
cpp
#include "Tasks/Task.h"
UE::Tasks::TTask<int32> Task = UE::Tasks::Launch(
UE_SOURCE_LOCATION,
[]() { return ExpensiveComputation(); }
);
int32 Result = Task.GetResult(); // 阻塞直至任务完成
// 带前置条件
UE::Tasks::TTask<FVector> TaskA = UE::Tasks::Launch(UE_SOURCE_LOCATION,
[]() { return ComputePosition(); });
UE::Tasks::TTask<void> TaskB = UE::Tasks::Launch(UE_SOURCE_LOCATION,
[&TaskA]() { ProcessPosition(TaskA.GetResult()); },
UE::Tasks::Prerequisites(TaskA)
);TTask<T> API: 会阻塞并返回结果。为非阻塞式检查。 / 用于带超时的阻塞等待。若任务未启动则在当前线程内联执行(工作窃取)。
GetResult()IsCompleted()Wait()Wait(FTimespan)TryRetractAndExecute()FTaskEvent用于手动同步——调用以解除依赖任务的阻塞。
Trigger()Async, TFuture, and TPromise
Async、TFuture与TPromise
Async()TFuture<T>cpp
TFuture<FMyResult> Future = Async(EAsyncExecution::ThreadPool,
[]() -> FMyResult { return ComputeResult(); },
[]() { /* completion callback — runs on unspecified thread */ }
);
FMyResult R = Future.Get(); // blocks, does NOT invalidate (unlike std::future)EAsyncExecution modes:
| Mode | Thread |
|---|---|
| Worker via TaskGraph |
| Game thread via TaskGraph |
| New dedicated thread |
| |
| |
Convenience: , .
AsyncPool(GThreadPool, Lambda)AsyncThread(Lambda, StackSize, Priority)Async()TFuture<T>cpp
TFuture<FMyResult> Future = Async(EAsyncExecution::ThreadPool,
[]() -> FMyResult { return ComputeResult(); },
[]() { /* 完成回调 — 在不确定的线程执行 */ }
);
FMyResult R = Future.Get(); // 阻塞,不会使Future失效(与std::future不同)EAsyncExecution模式:
| 模式 | 线程 |
|---|---|
| 通过TaskGraph调度至工作线程 |
| 通过TaskGraph调度至游戏线程 |
| 新的专用线程 |
| |
| |
便捷方法: 、。
AsyncPool(GThreadPool, Lambda)AsyncThread(Lambda, StackSize, Priority)TFuture<T> API
TFuture<T> API
Key difference from : does not invalidate. Call it multiple times safely. invalidates like .
std::futureGet()Consume()std::future::get()- -- non-blocking check
IsReady() - /
Wait()-- block without consumingWaitFor(FTimespan) - /
Then(Continuation)-- chaining, continuation runs on any threadNext(Continuation) - -- convert to shared future
Share()
与的关键区别:不会使Future失效。可安全多次调用。会像一样使Future失效。
std::futureGet()Consume()std::future::get()- -- 非阻塞式检查
IsReady() - /
Wait()-- 阻塞但不消费结果WaitFor(FTimespan) - /
Then(Continuation)-- 任务链,后续任务在任意线程执行Next(Continuation) - -- 转换为共享Future
Share()
TPromise<T>
TPromise<T>
For producer-consumer patterns where producing and consuming sides are decoupled.
cpp
TPromise<FMyData> Promise;
TFuture<FMyData> Future = Promise.GetFuture(); // call once
Async(EAsyncExecution::ThreadPool, [P = MoveTemp(Promise)]() mutable
{
P.SetValue(GenerateData()); // or EmplaceValue()
});
FMyData Result = Future.Get(); // blocks on game thread适用于生产者-消费者模式,其中生产端与消费端解耦。
cpp
TPromise<FMyData> Promise;
TFuture<FMyData> Future = Promise.GetFuture(); // 仅调用一次
Async(EAsyncExecution::ThreadPool, [P = MoveTemp(Promise)]() mutable
{
P.SetValue(GenerateData()); // 或使用EmplaceValue()
});
FMyData Result = Future.Get(); // 在游戏线程阻塞等待ParallelFor
ParallelFor
For data-parallel loops where each iteration is independent. The calling thread participates -- blocks until all iterations complete.
ParallelForcpp
ParallelFor(Meshes.Num(), [&Meshes](int32 Index)
{
ProcessMesh(Meshes[Index]);
});
// With MinBatchSize — prevents overhead for small workloads
ParallelFor(TEXT("ProcessMeshes"), Meshes.Num(), 64,
[&Meshes](int32 Index) { ProcessMesh(Meshes[Index]); }
);EParallelForFlags:
| Flag | Value | Effect |
|---|---|---|
| 0 | Default behavior |
| 1 | Debug: run sequentially |
| 2 | Iterations have variable cost |
| 4 | Pump render commands while waiting |
| 8 | Lower priority for workers |
ParallelForWithTaskContext适用于数据并行循环,其中每个迭代相互独立。调用线程会参与执行——会阻塞直至所有迭代完成。
ParallelForcpp
ParallelFor(Meshes.Num(), [&Meshes](int32 Index)
{
ProcessMesh(Meshes[Index]);
});
// 带MinBatchSize — 避免小工作量的额外开销
ParallelFor(TEXT("ProcessMeshes"), Meshes.Num(), 64,
[&Meshes](int32 Index) { ProcessMesh(Meshes[Index]); }
);EParallelForFlags:
| 标志 | 值 | 效果 |
|---|---|---|
| 0 | 默认行为 |
| 1 | 调试:串行执行 |
| 2 | 迭代成本可变 |
| 4 | 等待时处理渲染命令 |
| 8 | 降低工作线程优先级 |
ParallelForWithTaskContextGame Thread Safety
游戏线程安全
Threading bugs in UE are silent -- they corrupt state, cause GC races, and produce bugs that only reproduce under load. See for complete patterns.
references/thread-safety-guide.mdUE中的线程错误是静默的——它们会破坏状态、导致GC竞争,并产生仅在高负载下才会复现的bug。如需完整的模式,请查看。
references/thread-safety-guide.mdUObject Access Rules
UObject访问规则
- All UObject access must happen on the game thread. No reads, writes, or function calls from background threads.
- GC can destroy UObjects between ticks. A raw captured in a lambda may be dangling by execution time.
UObject* - Spawning, destroying, modifying components -- game thread only.
- 所有UObject访问必须在游戏线程中进行。禁止在后台线程中读取、写入或调用UObject的函数。
- GC可能在帧间隔销毁UObject。lambda中捕获的原始在执行时可能已悬空。
UObject* - 生成、销毁、修改组件 — 仅可在游戏线程中进行。
Safe Dispatch Pattern
安全调度模式
cpp
AsyncTask(ENamedThreads::GameThread, [WeakActor = TWeakObjectPtr<AActor>(MyActor)]()
{
if (AActor* Actor = WeakActor.Get()) // nullptr if GC'd
{
Actor->UpdateFromBackgroundWork(NewData);
}
});Always capture , never raw . For non-UObject shared data, use -- the default has non-atomic refcounting.
TWeakObjectPtrUObject*TSharedPtr<T, ESPMode::ThreadSafe>ESPMode::NotThreadSafecpp
AsyncTask(ENamedThreads::GameThread, [WeakActor = TWeakObjectPtr<AActor>(MyActor)]()
{
if (AActor* Actor = WeakActor.Get()) // 若已被GC则为nullptr
{
Actor->UpdateFromBackgroundWork(NewData);
}
});始终捕获,绝不要捕获原始。对于非UObject的共享数据,使用——默认的使用非原子引用计数。
TWeakObjectPtrUObject*TSharedPtr<T, ESPMode::ThreadSafe>ESPMode::NotThreadSafeSynchronization Primitives
同步原语
FCriticalSection (Recursive Mutex)
FCriticalSection(递归互斥锁)
FCriticalSectionUE::FPlatformRecursiveMutexcpp
FCriticalSection DataLock;
void AddPosition(const FVector& Pos)
{
FScopeLock Lock(&DataLock); // RAII — unlocks on scope exit
SharedPositions.Add(Pos);
}FCriticalSectionUE::FPlatformRecursiveMutexcpp
FCriticalSection DataLock;
void AddPosition(const FVector& Pos)
{
FScopeLock Lock(&DataLock); // RAII — 作用域结束时自动解锁
SharedPositions.Add(Pos);
}FRWLock (Read-Write Lock)
FRWLock(读写锁)
Multiple readers OR one exclusive writer. is not recursive -- do not nest.
FRWLockcpp
FRWLock CacheLock;
FVector Read(FName Key) { FReadScopeLock RL(CacheLock); return Cache.FindRef(Key); }
void Write(FName K, FVector V) { FWriteScopeLock WL(CacheLock); Cache.Add(K, V); }支持多个读者或一个独占写者。不可递归——不要嵌套锁定。
FRWLockcpp
FRWLock CacheLock;
FVector Read(FName Key) { FReadScopeLock RL(CacheLock); return Cache.FindRef(Key); }
void Write(FName K, FVector V) { FWriteScopeLock WL(CacheLock); Cache.Add(K, V); }FEventRef and Atomics
FEventRef与原子操作
FEventRefFEvent*cpp
FEventRef WorkReady(EEventMode::AutoReset);
WorkReady->Trigger(); // producer signals
WorkReady->Wait(); // consumer blocksFThreadSafeCounterFThreadSafeBoolstd::atomic<int32>std::atomic<bool>FEventRefFEvent*cpp
FEventRef WorkReady(EEventMode::AutoReset);
WorkReady->Trigger(); // 生产者发送信号
WorkReady->Wait(); // 消费者阻塞等待FThreadSafeCounterFThreadSafeBoolstd::atomic<int32>std::atomic<bool>TQueue (Lock-Free Queue)
TQueue(无锁队列)
Thread-safe queue for producer-consumer without locks.
cpp
TQueue<FMyMessage, EQueueMode::Mpsc> MessageQueue;
// Producer (any thread)
MessageQueue.Enqueue(FMyMessage{...});
// Consumer (game thread tick)
FMyMessage Msg;
while (MessageQueue.Dequeue(Msg)) { ProcessMessage(Msg); }SpscMpscPeek()用于生产者-消费者模式的线程安全队列,无需锁。
cpp
TQueue<FMyMessage, EQueueMode::Mpsc> MessageQueue;
// 生产者(任意线程)
MessageQueue.Enqueue(FMyMessage{...});
// 消费者(游戏线程Tick)
FMyMessage Msg;
while (MessageQueue.Dequeue(Msg)) { ProcessMessage(Msg); }SpscMpscPeek()Common Mistakes
常见错误
Accessing UObject from background thread -- dispatch results back:
cpp
// WRONG — UObject access off game thread
Async(EAsyncExecution::ThreadPool, [this]()
{ MyActor->Health = ComputeNewHealth(); });
// RIGHT — compute off-thread, apply on game thread via weak pointer
Async(EAsyncExecution::ThreadPool, [WeakActor = TWeakObjectPtr<AActor>(MyActor)]()
{
float NewHealth = ComputeNewHealth();
AsyncTask(ENamedThreads::GameThread, [WeakActor, NewHealth]()
{ if (AActor* A = WeakActor.Get()) { A->SetHealth(NewHealth); } });
});TSharedPtr with default ESPMode across threads:
cpp
// WRONG — non-atomic refcount
auto Data = MakeShared<FMyData>();
// RIGHT
auto Data = MakeShared<FMyData, ESPMode::ThreadSafe>();FRWLock nested acquisition -- deadlock:
cpp
// WRONG — FRWLock is NOT recursive
FReadScopeLock Outer(Lock);
FReadScopeLock Inner(Lock); // DEADLOCK on some platforms
// RIGHT — acquire once, do all reads, releaseParallelFor with shared mutable state:
cpp
// WRONG — concurrent writes
int32 Total = 0;
ParallelFor(Data.Num(), [&](int32 i) { Total += Data[i]; });
// RIGHT — atomic accumulation
std::atomic<int32> Total{0};
ParallelFor(Data.Num(), [&](int32 i)
{ Total.fetch_add(Data[i], std::memory_order_relaxed); });在后台线程访问UObject — 需将结果调度回游戏线程:
cpp
// 错误 — 在非游戏线程访问UObject
Async(EAsyncExecution::ThreadPool, [this]()
{ MyActor->Health = ComputeNewHealth(); });
// 正确 — 在后台线程计算,通过弱指针将结果应用到游戏线程
Async(EAsyncExecution::ThreadPool, [WeakActor = TWeakObjectPtr<AActor>(MyActor)]()
{
float NewHealth = ComputeNewHealth();
AsyncTask(ENamedThreads::GameThread, [WeakActor, NewHealth]()
{ if (AActor* A = WeakActor.Get()) { A->SetHealth(NewHealth); } });
});跨线程使用默认ESPMode的TSharedPtr:
cpp
// 错误 — 非原子引用计数
auto Data = MakeShared<FMyData>();
// 正确
auto Data = MakeShared<FMyData, ESPMode::ThreadSafe>();嵌套获取FRWLock — 死锁:
cpp
// 错误 — FRWLock不可递归
FReadScopeLock Outer(Lock);
FReadScopeLock Inner(Lock); // 在部分平台会导致死锁
// 正确 — 仅获取一次锁,完成所有读取后释放ParallelFor中使用共享可变状态:
cpp
// 错误 — 并发写入
int32 Total = 0;
ParallelFor(Data.Num(), [&](int32 i) { Total += Data[i]; });
// 正确 — 原子累加
std::atomic<int32> Total{0};
ParallelFor(Data.Num(), [&](int32 i)
{ Total.fetch_add(Data[i], std::memory_order_relaxed); });Related Skills
相关技能
- -- TSharedPtr, TWeakObjectPtr, GC lifetime, smart pointer rules
ue-cpp-foundations - -- game thread tick flow, actor lifecycle ordering
ue-gameplay-framework - -- RPC dispatch threads, replication callbacks
ue-networking-replication - -- FStreamableManager async loading, soft references
ue-data-assets-tables
- — TSharedPtr、TWeakObjectPtr、GC生命周期、智能指针规则
ue-cpp-foundations - — 游戏线程Tick流程、Actor生命周期顺序
ue-gameplay-framework - — RPC调度线程、复制回调
ue-networking-replication - — FStreamableManager异步加载、软引用
ue-data-assets-tables