FRunnable and Threads

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