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