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
};