Threads & FRunnable - One actor run correct the other does not but both are the same class?

Hello,

I am struggling with two actors of the same class (AMyActor) that use FRunnable to create a thread for background processing to create a mesh. When the two actors are placed in the same level, the first actor runs the thread correctly and produces a mesh but the second reports that the Runnable variable is not null and therefore does not create its new class and fails.

What I want to acheive is store a pointer to the thread in MyActor.h and create the new thread in StartCreateMesh(). Then I can use that pointer to stop the thread using Shutdown() or let it run until complete and Shut itself down.

CreateMeshTasks.h


class FCreateMeshTask : public FRunnable
{
//Singleton instance. Can access the thread at any time using static accessors.
static FCreateMeshTask* Runnable;

//Thread to run the worker on
FRunnableThread* Thread;

//Thread safe counter to stop the thread
FThreadSafeCounter StopTaskCounter;

public:

//Constructor
FCreateMeshTask::FCreateMeshTask(int32 x, int32 y, FMeshCallback& callback)
: StopTaskCounter(0)
{
X = x;
Y = y;

Callback = callback;

//Create the new thread
Thread = FRunnableThread::Create(this, TEXT("FCreateMeshTask"), 0, TPri_AboveNormal);
}

//Destructor
virtual ~FCreateMeshTask()
{
delete Thread;
}

//Implement the FRunnable interface
virtual bool Init() override
{
return true;
}

virtual uint32 Run();

virtual void Exit() override
{
FMeshTaskResult result;
result.Columns = TArray<FTableIndices>(MeshColumns);
result.Rows = TArray<FTableIndices>(MeshRows);

AsyncTask(ENamedThreads::GameThread, [this, result]()
{
if (Callback.IsBound())
Callback.Execute(result);
});
}

virtual void Stop() override
{
StopTaskCounter.Increment();
}

FORCEINLINE bool ShouldStop() const
{
return StopTaskCounter.GetValue() > 0;
}

//Make sure this thread has stopped
void EnsureCompletion()
{
Stop();
Thread->WaitForCompletion();
}

public:

static FCreateMeshTask* StartWorker(int32 x, int32 y, FMeshCallback& callback)
{
//This Runnable var reports not nullptr in the second actor and fails
if (Runnable == nullptr && FPlatformProcess::SupportsMultithreading())
{
Runnable = new FCreateMeshTask(x, y, callback);
}

return Runnable;
}

//Shuts down the thread
static void Shutdown()
{
if (Runnable)
{
Runnable->EnsureCompletion();
delete Runnable;
Runnable = nullptr;
}
}

//Init all vars
int32 X = 0;
int32 Y = 0;
FMeshCallback Callback;
}

FCreateMeshTask* FCreateMeshTask::Runnable = nullptr;

CreateMeshTasks.cpp


uint32 FCreateMeshTask::Run()
{
//Initial wait
FPlatformProcess::Sleep(0.05f);

for (int32 x = 0; x <= X; x++)
for (int32 y = 0; y <= Y; y++)
{
if (ShouldStop())
return 0;

//More code here
}
}

return 0;
}

MyActor.h


FCreateMeshTask* CreateMeshTaskWorker;

MyActor.cpp


void AMyActor::StartCreateMesh()
{
if (!createMeshCompletedCallback.IsBound())
createMeshCompletedCallback = FMeshCallback::CreateUObject(this, &AMyActor::CreateMeshTaskCompleted);

CreateMeshTaskWorker = FCreateMeshTask::StartWorker(X, Y, createMeshCompletedCallback);
}

void AMyActor::CreateMeshTaskCompleted(FMeshTaskResult meshResult)
{
//Shutdown the FCreateMeshTask thread since it's finished
CreateMeshTaskWorker->Shutdown();

//More processing
}

I can’t understand why two separate actors that each create a thread do not seem to be able to run at the same time. One reports the Runnable variable as NULL and runs correctly while the second then report the Runnable var as not null and fails.

I tried adding a random number to the end of the thread name using FMath::Rand() and FString::Printf which did create the name plus a number but the problem still existed.

I could use some help, thank you :slight_smile:

As your code comment points it out its a singleton, you can’t create it more than once unless you’ve destroyed it in the meantime. Static variables live in class scope and singletons are anti pattern of multithreading, you should read upon static and singleton then redesign your code afterwards.