ue-async-threading

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

UE 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
.agents/ue-project-context.md
before proceeding. Engine version matters:
UE::Tasks::Launch
is the modern preferred API (UE 5.0+), while
FAsyncTask
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.md
。引擎版本很重要:
UE::Tasks::Launch
是现代推荐使用的API(UE 5.0+),而
FAsyncTask
和TaskGraph仍完全受支持。需要确定:哪些工作需要卸载?是否需要访问UObject?可接受的延迟/吞吐量权衡是什么?

Information 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
GetWorld()
, no spawning from background threads. Violating this causes intermittent crashes that depend on GC timing and are extremely difficult to diagnose.

UE运行多个命名线程以及一个可扩展的工作线程池。了解哪个线程拥有哪些资源可以避免最常见的线程错误。
命名线程:
  • Game Thread — 所有UObject访问、蓝图执行、游戏玩法逻辑都在此线程进行。可通过
    IsInGameThread()
    检查当前是否处于该线程。
  • Render Thread — 渲染命令、场景代理更新。可通过
    IsInRenderingThread()
    检查。
  • RHI Thread — GPU命令提交(取决于平台)。
  • Worker Threads — 用于任务调度的无名池线程。线程数量随CPU核心数扩展。
黄金法则: UObject仅可在游戏线程中访问。禁止在后台线程中读取UPROPERTY、调用UFUNCTION、使用
GetWorld()
或生成对象。违反此规则会导致间歇性崩溃,这类崩溃依赖GC时机,极难诊断。

Pattern Selection Guide

模式选择指南

Choose the simplest API that fits your needs.
PatternBest ForLifetimeResult?
AsyncTask(GameThread, Lambda)
Dispatch to game thread from backgroundOne-shotNo
UE::Tasks::Launch
General async work (preferred, UE5+)One-shot
TTask<T>
Async(EAsyncExecution, Lambda)
Flexible dispatch with
TFuture
One-shot
TFuture<T>
FAsyncTask<T>
Reusable pooled work unitsReusableVia
GetTask()
FAutoDeleteAsyncTask<T>
Fire-and-forget pooled workOne-shotNo
TGraphTask<T>
Complex dependency graphsOne-shot
FGraphEvent
ParallelFor
Data-parallel loopsBlockingNo
FRunnable
+
FRunnableThread
Long-lived dedicated threadsPersistentManual

选择最符合需求的最简API。
模式最佳适用场景生命周期是否返回结果
AsyncTask(GameThread, Lambda)
从后台线程向游戏线程调度任务一次性
UE::Tasks::Launch
通用异步工作(推荐,UE5+)一次性
TTask<T>
Async(EAsyncExecution, Lambda)
TFuture
的灵活调度
一次性
TFuture<T>
FAsyncTask<T>
可复用的池化工作单元可复用通过
GetTask()
FAutoDeleteAsyncTask<T>
即发即弃的池化工作一次性
TGraphTask<T>
复杂依赖图一次性
FGraphEvent
ParallelFor
数据并行循环阻塞式
FRunnable
+
FRunnableThread
长期运行的专用线程持久化手动处理

FRunnable and FRunnableThread

FRunnable与FRunnableThread

Use
FRunnable
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
UE::Tasks::Launch
or
FAsyncTask
.
Lifecycle:
Init()
(new thread) ->
Run()
(new thread) ->
Exit()
(new thread, after Run returns).
Stop()
is called externally to request shutdown.
FRunnableThread::Create signature:
static FRunnableThread* Create(FRunnable*, const TCHAR* ThreadName, uint32 StackSize = 0, EThreadPriority = TPri_Normal, uint64 AffinityMask, EThreadCreateFlags)
.
Key points:
Stop()
signals the thread -- it does not block.
Kill(true)
calls
Stop()
then waits for completion. Always
delete
the
FRunnableThread*
after
Kill
. Use
std::atomic<bool> bShouldStop
in
Run()
loop, set it in
Stop()
.
See
references/threading-patterns.md
for a complete
FRunnable
subclass template with proper shutdown.

仅当你需要专用的长期运行线程(如套接字监听器、文件监视器或持续处理循环)时,才使用
FRunnable
。对于一次性工作,优先选择
UE::Tasks::Launch
FAsyncTask
生命周期:
Init()
(新线程中执行)->
Run()
(新线程中执行)->
Exit()
(新线程中执行,Run返回后触发)。
Stop()
由外部调用以请求关闭。
FRunnableThread::Create签名:
static FRunnableThread* Create(FRunnable*, const TCHAR* ThreadName, uint32 StackSize = 0, EThreadPriority = TPri_Normal, uint64 AffinityMask, EThreadCreateFlags)
关键点:
Stop()
仅向线程发送信号——不会阻塞。
Kill(true)
会调用
Stop()
然后等待完成。
Kill
后务必
delete
对应的
FRunnableThread*
。在
Run()
循环中使用
std::atomic<bool> bShouldStop
,并在
Stop()
中设置其值。
如需完整的
FRunnable
子类模板及正确的关闭逻辑,请查看
references/threading-patterns.md

FAsyncTask and FAutoDeleteAsyncTask

FAsyncTask与FAutoDeleteAsyncTask

For reusable work units on the engine thread pool (
GThreadPool
). Subclass
FNonAbandonableTask
and implement
DoWork()
+
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()
is the non-blocking completion check.
Cancel()
prevents execution if not yet started.
StartSynchronousTask()
runs inline on the calling thread.

适用于引擎线程池(
GThreadPool
)上的可复用工作单元。继承
FNonAbandonableTask
并实现
DoWork()
+
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:
GetResult()
blocks and returns result.
IsCompleted()
non-blocking.
Wait()
/
Wait(FTimespan)
for timed blocking.
TryRetractAndExecute()
runs inline if not yet started (work stealing).
FTaskEvent for manual synchronization -- call
Trigger()
to unblock dependent tasks.

推荐用于新代码(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()
is the most flexible one-shot dispatch. Returns
TFuture<T>
with execution context control.
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:
ModeThread
TaskGraph
Worker via TaskGraph
TaskGraphMainThread
Game thread via TaskGraph
Thread
New dedicated thread
ThreadPool
GThreadPool
worker
LargeThreadPool
GLargeThreadPool
(WITH_EDITOR only)
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调度至工作线程
TaskGraphMainThread
通过TaskGraph调度至游戏线程
Thread
新的专用线程
ThreadPool
GThreadPool
工作线程
LargeThreadPool
GLargeThreadPool
(仅编辑器模式可用)
便捷方法:
AsyncPool(GThreadPool, Lambda)
AsyncThread(Lambda, StackSize, Priority)

TFuture<T> API

TFuture<T> API

Key difference from
std::future
:
Get()
does not invalidate. Call it multiple times safely.
Consume()
invalidates like
std::future::get()
.
  • IsReady()
    -- non-blocking check
  • Wait()
    /
    WaitFor(FTimespan)
    -- block without consuming
  • Then(Continuation)
    /
    Next(Continuation)
    -- chaining, continuation runs on any thread
  • Share()
    -- convert to shared future
std::future
的关键区别:
Get()
不会使Future失效。可安全多次调用。
Consume()
会像
std::future::get()
一样使Future失效。
  • IsReady()
    -- 非阻塞式检查
  • Wait()
    /
    WaitFor(FTimespan)
    -- 阻塞但不消费结果
  • Then(Continuation)
    /
    Next(Continuation)
    -- 任务链,后续任务在任意线程执行
  • Share()
    -- 转换为共享Future

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 --
ParallelFor
blocks until all iterations complete.
cpp
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:
FlagValueEffect
None
0Default behavior
ForceSingleThread
1Debug: run sequentially
Unbalanced
2Iterations have variable cost
PumpRenderingThread
4Pump render commands while waiting
BackgroundPriority
8Lower priority for workers
ParallelForWithTaskContext
provides a per-worker context object -- use when workers need scratch memory to avoid per-iteration allocation.

适用于数据并行循环,其中每个迭代相互独立。调用线程会参与执行——
ParallelFor
会阻塞直至所有迭代完成。
cpp
ParallelFor(Meshes.Num(), [&Meshes](int32 Index)
{
    ProcessMesh(Meshes[Index]);
});

// 带MinBatchSize — 避免小工作量的额外开销
ParallelFor(TEXT("ProcessMeshes"), Meshes.Num(), 64,
    [&Meshes](int32 Index) { ProcessMesh(Meshes[Index]); }
);
EParallelForFlags:
标志效果
None
0默认行为
ForceSingleThread
1调试:串行执行
Unbalanced
2迭代成本可变
PumpRenderingThread
4等待时处理渲染命令
BackgroundPriority
8降低工作线程优先级
ParallelForWithTaskContext
提供每个工作线程的上下文对象——当工作线程需要临时内存以避免每次迭代分配时使用。

Game Thread Safety

游戏线程安全

Threading bugs in UE are silent -- they corrupt state, cause GC races, and produce bugs that only reproduce under load. See
references/thread-safety-guide.md
for complete patterns.
UE中的线程错误是静默的——它们会破坏状态、导致GC竞争,并产生仅在高负载下才会复现的bug。如需完整的模式,请查看
references/thread-safety-guide.md

UObject Access Rules

UObject访问规则

  1. All UObject access must happen on the game thread. No reads, writes, or function calls from background threads.
  2. GC can destroy UObjects between ticks. A raw
    UObject*
    captured in a lambda may be dangling by execution time.
  3. Spawning, destroying, modifying components -- game thread only.
  1. 所有UObject访问必须在游戏线程中进行。禁止在后台线程中读取、写入或调用UObject的函数。
  2. GC可能在帧间隔销毁UObject。lambda中捕获的原始
    UObject*
    在执行时可能已悬空。
  3. 生成、销毁、修改组件 — 仅可在游戏线程中进行。

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
TWeakObjectPtr
, never raw
UObject*
. For non-UObject shared data, use
TSharedPtr<T, ESPMode::ThreadSafe>
-- the default
ESPMode::NotThreadSafe
has non-atomic refcounting.

cpp
AsyncTask(ENamedThreads::GameThread, [WeakActor = TWeakObjectPtr<AActor>(MyActor)]()
{
    if (AActor* Actor = WeakActor.Get()) // 若已被GC则为nullptr
    {
        Actor->UpdateFromBackgroundWork(NewData);
    }
});
始终捕获
TWeakObjectPtr
,绝不要捕获原始
UObject*
。对于非UObject的共享数据,使用
TSharedPtr<T, ESPMode::ThreadSafe>
——默认的
ESPMode::NotThreadSafe
使用非原子引用计数。

Synchronization Primitives

同步原语

FCriticalSection (Recursive Mutex)

FCriticalSection(递归互斥锁)

FCriticalSection
is
UE::FPlatformRecursiveMutex
. Same thread can lock multiple times without deadlocking.
cpp
FCriticalSection DataLock;
void AddPosition(const FVector& Pos)
{
    FScopeLock Lock(&DataLock);  // RAII — unlocks on scope exit
    SharedPositions.Add(Pos);
}
FCriticalSection
UE::FPlatformRecursiveMutex
。同一线程可多次锁定而不会死锁。
cpp
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.
FRWLock
is not recursive -- do not nest.
cpp
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); }
支持多个读者一个独占写者。
FRWLock
不可递归——不要嵌套锁定。
cpp
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与原子操作

FEventRef
is the RAII wrapper for thread signaling. Prefer over raw
FEvent*
.
cpp
FEventRef WorkReady(EEventMode::AutoReset);
WorkReady->Trigger(); // producer signals
WorkReady->Wait();    // consumer blocks
FThreadSafeCounter
and
FThreadSafeBool
are deprecated -- use
std::atomic<int32>
and
std::atomic<bool>
directly.

FEventRef
是线程信号的RAII包装器。优先使用它而非原始
FEvent*
cpp
FEventRef WorkReady(EEventMode::AutoReset);
WorkReady->Trigger(); // 生产者发送信号
WorkReady->Wait();    // 消费者阻塞等待
FThreadSafeCounter
FThreadSafeBool
已废弃——直接使用
std::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); }
Spsc
-- single-producer, single-consumer (slightly faster).
Mpsc
-- multiple-producer, single-consumer (most common).
Peek()
reads without dequeuing.

用于生产者-消费者模式的线程安全队列,无需锁。
cpp
TQueue<FMyMessage, EQueueMode::Mpsc> MessageQueue;

// 生产者(任意线程)
MessageQueue.Enqueue(FMyMessage{...});

// 消费者(游戏线程Tick)
FMyMessage Msg;
while (MessageQueue.Dequeue(Msg)) { ProcessMessage(Msg); }
Spsc
— 单生产者单消费者(速度略快)。
Mpsc
— 多生产者单消费者(最常用)。
Peek()
可读取但不移除元素。

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, release
ParallelFor 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

相关技能

  • ue-cpp-foundations
    -- TSharedPtr, TWeakObjectPtr, GC lifetime, smart pointer rules
  • ue-gameplay-framework
    -- game thread tick flow, actor lifecycle ordering
  • ue-networking-replication
    -- RPC dispatch threads, replication callbacks
  • ue-data-assets-tables
    -- FStreamableManager async loading, soft references
  • ue-cpp-foundations
    — TSharedPtr、TWeakObjectPtr、GC生命周期、智能指针规则
  • ue-gameplay-framework
    — 游戏线程Tick流程、Actor生命周期顺序
  • ue-networking-replication
    — RPC调度线程、复制回调
  • ue-data-assets-tables
    — FStreamableManager异步加载、软引用