This short article addresses the question of how to work with UObjects in a thread-safe way when passing them to some workers, asynchronous tasks, thread pools, or whatever else using a non-game thread.
One of the most important issues here is garbage collection. When we pass a UObject* that is not set to root directly to a background thread, it is possible that the passed UObject may be silently deleted by the garbage collector. Even if we check the validity of a UObject (.IsValidLowLevel()
) on a background thread very often, we cannot be sure that this object will remain alive, for example, a few milliseconds after this check.
An example of improper use of UObject on a background thread
UObject* CreatedObject = NewObject<UObject>();
AsyncTask(ENamedThreads::AnyThread, [CreatedObject]()
{
// Use of CreatedObject...
// Here we cannot be sure that the CreatedObject will remain alive during the execution of the logic
};
The approach to properly handle this is to use FGCObjectScopeGuard
which ensures that your object is not garbage collected in your async scope:
An example of proper use of UObject on a background thread
UObject* CreatedObject = NewObject<UObject>();
AsyncTask(ENamedThreads::AnyThread, [CreatedObject]()
{
FGCObjectScopeGuard CreatedObjectGuard(CreatedObject);
// Use of CreatedObject...
// Here we are sure that the CreatedObject will not be garbage collected within this scope
};