There are a bunch of ways to do threads in Unreal. Today we’re going to cover possibly the simplest one, FRunnable.

I updated this example based on how FRunnable is used in the UE4 source code. If you need more examples of FRunnable, I strongly recommend taking a look.

BUIExampleRunnable.h

#pragma once

#include "CoreMinimal.h"
#include "HAL/Runnable.h"

DECLARE_DYNAMIC( FBUIOnCompleteSignature );

// Note that we do not have to mark our class as UCLASS() if we don't want to
class FBUIExampleRunnable : public FRunnable
{
public:
	// Custom constructor for setting up our thread with its target
	FBUIExampleRunnable(int32 InTargetCount);
	virtual ~FBUIExampleRunnable();

	// FRunnable functions
	virtual uint32 Run() override;
	virtual void Stop() override;
	virtual void Exit() override;
	// FRunnable

	FBUIOnCompleteSignature OnCompleteDelegate;

protected:
	FRunnableThread* Thread = nullptr;

	TArray<int32> ProcessedNumbers;

	int32 TargetCount = -1;
	int32 FoundCount = -1;

	bool bStopThread = false;
};

BUIExampleRunnable.cpp

#include "ThreadExample.h"

FBUIExampleRunnable::FBUIExampleRunnable( int32 InTargetCount )
{
	TargetCount = InTargetCount;
	FoundCount = 0;
	Thread = FRunnableThread::Create( this, TEXT( "This is my thread example" ) );
}

FBUIExampleRunnable::~FBUIExampleRunnable()
{
	if ( Thread != nullptr )
	{
		Thread->Kill( true );
		delete Thread;
	}
}

uint32 FBUIExampleRunnable::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 && FoundCount < TargetCount )
	{
		// This is where we would do our expensive threaded processing

		// Instead we're going to make a really 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;
	}

	OnCompleteDelegate.ExecuteIfBound();

	// Return success
	return 0;
}


void FBUIExampleRunnable::Exit()
{
	// Here's where we can do any cleanup we want to 
}


void FBUIExampleRunnable::Stop()
{
	// Force our thread to stop early
	bStopThread = true;
}

Running our code

TSharedPtr<FBUIExampleRunnable> SomeRunnable = MakeShareable<FBUIExampleRunnable>( 10 );