ue-async-threading

Use this skill when working with Unreal Engine async operations, threading, parallel execution, or concurrency. Also use when the user mentions 'FRunnable', 'FAsyncTask', 'TaskGraph', 'UE::Tasks', 'ParallelFor', 'TFuture', 'TPromise', 'Async()', 'thread safety', 'FCriticalSection', 'FRWLock', 'backg

By quodsoler · 792 installs

npx skills add quodsoler/unreal-engine-skills --skill ue-async-threading

Source repository · Upstream listing

UE Async and Threading You are an expert in Unreal Engine's threading model, async task systems, and concurrent programming patterns. 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? 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? UE Threading Model 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. Pattern Selection Guide Choose the simplest API that fits your needs. Pattern Best For Lifetime Result? AsyncTask(GameThread, Lambda) Dispatch to game thread from background One shot No 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 units Reusable Via GetTask() FAutoDeleteAsyncTask<T Fire and forget pooled work One shot No TGraphTask<T Complex dependency graphs One shot FGraphEvent ParallelFor Data parallel loops Blocking No FRunnable + FRunnableThread Long lived dedicated threads Persistent Manual FRunnable and 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. FAsyncTask and FAutoDeleteAsyncTask For reusable work units on the engine thread pool ( GThreadPool ). Subclass FNonAbandonableTask and implement DoWork() + GetStatId() . Usage: IsWorkDone() is the non blocking completion check. Cancel() prevents execution if not yet started. StartSynchronousTask() runs inline on the calling thread. TaskGraph For work with complex dependency chains . Each task declares prerequisites; the scheduler handles ordering. Dispatching with prerequisites: Quick dispatch (no custom class needed): UE::Tasks::Launch (Modern Preferred API) Recommended for new code (UE 5.0+). Simpler syntax than TaskGraph, automatic thread pool dispatch, built in chaining. 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. Async, TFuture, and TPromise Async() is the most flexible one shot dispatch. Returns TFuture<T with execution context control. EAsyncExecution modes: Mode Thread 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) . 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 TPromise<T For producer consumer patterns where producing and consuming sides are decoupled. ParallelFor For data parallel loops where each iteration is independent. The calling thread participates ParallelFor blocks until all iterations complete. EParallelForFlags: Flag Value Effect None 0 Default behavior ForceSingleThread 1 Debug: run sequentially Unbalanced 2 Iterations have variable cost PumpRenderingThread 4 Pump render commands while waiting BackgroundPriority 8 Lower priority for workers ParallelForWithTaskContext provides a per worker context object use when workers need scratch memory to avoid per iteration allocation. 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. UObject Access Rules 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. Safe Dispatch Pattern Always capture TWeakObjectPtr , never raw UObject . For non UObject shared data, use TSharedPtr<T, ESPMode::ThreadSafe the default ESPMode::NotThreadSafe has non atomic refcounting. Synchronization Primitives FCriticalSection (Recursive Mutex) FCriticalSection is UE::FPlatformRecursiveMutex . Same thread can lock multiple times without deadlocking. FRWLock (Read Write Lock) Multiple readers OR one exclusive writer. FRWLock is not recursive do not nest. FEventRef and Atomics FEventRef is the RAII wrapper for thread signaling. Prefer over raw FEvent . FThreadSafeCounter and FThreadSafeBool are deprecated use std::atomic<int32 and std::atomic<bool directly. TQueue (Lock Free Queue) Thread safe queue for producer consumer without locks. Spsc single producer, single consumer (slightly faster). Mpsc multiple producer, single consumer (most common). Peek() reads without dequeuing. Common Mistakes Accessing UObject from background thread dispatch results back: TSharedPtr with default ESPMode across threads: FRWLock nested acquisition deadlock: ParallelFor with shared mutable state: 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