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 crucial issues is garbage collection. When we pass a UObject
that is not set to root directly to a background thread, it’s possible that the garbage collector may silently delete the passed UObject
. Even if we frequently check the validity of the UObject
(IsValidLowLevel()
) on a background thread, we cannot guarantee that this object will remain alive, even just a few milliseconds after the check.
An example of incorrect usage 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 proper way to handle this is to use FGCObjectScopeGuard
, which ensures that the object is not garbage collected within the asynchronous 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
};
While adding an object to UPROPERTY
can prevent it from being garbage collected, it is still possible for its parent object to be collected, leading to the needed object being collected as well. That’s why using FGCObjectScopeGuard
is recommended to ensure the object remains alive within the scope, except when executing the logic in the game thread.
Note that the FGCObjectScopeGuard
may cause a crash if the object being guarded is forcibly deleted by the garbage collector, which is particularly relevant when exiting the In-Editor Testing
mode.