There are a bunch of ways to do threads in Unreal. Today we’re going to cover
possibly the simplest one, FRunnable
.
Our very simple example consists of two classes, UThreadManager
that creates
a thread and runs it, and ThreadExample
, that does some expensive processing.
I’ll let the code and comments do most of the explaining.
ThreadManager.h
Create an instance of this and call `StartProcess()` on it, and call `PrintStuff()` every frame to test out the example.
#pragma once
#include "ThreadManager.generated.h"
UCLASS()
class UThreadManager : public UObject
{
GENERATED_BODY()
public:
UThreadManager(const FObjectInitializer& ObjectInitializer);
// Call this to create the thread and start it going
void StartProcess();
// Call this to print the current state of the thread
void PrintStuff();
protected:
bool IsComplete() const;
class ThreadExample* MyThreadExample = nullptr;
FRunnableThread* CurrentThread = nullptr;
};
ThreadManager.cpp
#include "MyProjectPCH.h"
#include "HAL/RunnableThread.h"
#include "ThreadExample.h"
#include "ThreadManager.h"
UThreadManager::UThreadManager(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UThreadManager::StartProcess()
{
if (!CurrentThread && FPlatformProcess::SupportsMultithreading())
{
// Run the thread until we've found 999 random numbers
MyThreadExample = new ThreadExample(999);
CurrentThread = FRunnableThread::Create(MyThreadExample, TEXT("Any old thread name"));
}
}
bool UThreadManager::IsComplete() const
{
return !CurrentThread || MyThreadExample->IsComplete();
}
void UThreadManager::PrintStuff()
{
if (!CurrentThread || !MyThreadExample)
return;
if (IsComplete())
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 0, FColor::Green,
FString::Printf(TEXT("Numbers generated:: %d, First 3 are: %d, %d, %d"),
MyThreadExample->ProcessedNumbers.Num(),
(MyThreadExample->ProcessedNumbers.Num() > 0)
? MyThreadExample->ProcessedNumbers[0] : -1,
(MyThreadExample->ProcessedNumbers.Num() > 1)
? MyThreadExample->ProcessedNumbers[1] : -1,
(MyThreadExample->ProcessedNumbers.Num() > 2)
? MyThreadExample->ProcessedNumbers[2] : -1));
}
}
else
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 0, FColor::Green,
FString::Printf(TEXT("Still processing: %d"),
MyThreadExample->ProcessedNumbers.Num()));
}
}
}
ThreadExample.h
#pragma once
#include "HAL/Runnable.h"
// Note that we do not have to mark our class as UCLASS() if we don't want to
class ThreadExample : public FRunnable
{
public:
// Custom constructor for setting up our thread with its target
ThreadExample(int32 InTargetCount);
// FRunnable functions
virtual uint32 Run() override;
virtual void Stop() override;
virtual void Exit() override;
// FRunnable
TArray<int32> ProcessedNumbers;
bool IsComplete() const;
protected:
int32 TargetCount = -1;
int32 FoundCount = -1;
bool bStopThread = false;
};
ThreadExample.cpp
#include "MyProjectPCH.h"
#include "ThreadExample.h"
ThreadExample::ThreadExample(int32 InTargetCount)
{
TargetCount = InTargetCount;
FoundCount = 0;
}
uint32 ThreadExample::Run()
{
bStopThread = false;
// Keep processing until we're cancelled through Stop() or we're done,
// although this thread will suspended for other stuff to happen at the same time
while (!bStopThread && !IsComplete())
{
// This is where we would do our expensive threaded processing
// Instead we're going to make a reaaaally busy while loop to slow down processing
// You can change INT_MAX to something smaller if you want it to run faster
int32 x = 0;
while (x < INT_MAX)
{
x++;
}
ProcessedNumbers.Add(FMath::RandRange(0, 999));
FoundCount += 1;
}
// Return success
return 0;
}
void ThreadExample::Exit()
{
// Here's where we can do any cleanup we want to
}
void ThreadExample::Stop()
{
// Force our thread to stop early
bStopThread = true;
}
bool ThreadExample::IsComplete() const
{
return FoundCount >= TargetCount;
}