MultiThreading in C++ with UE5

|>|UnrealEditor-chessCode-0157.dll!MyThread::Run() Line 31|C++|
|---|---|---|
| |UnrealEditor-chessCode-0157.dll!AMyPawn::BeginPlay() Line 61|C++|
| |UnrealEditor-Engine.dll!AActor::DispatchBeginPlay(bool bFromLevelStreaming) Line 3849|C++|
| |UnrealEditor-Engine.dll!AWorldSettings::NotifyBeginPlay() Line 283|C++|
| |UnrealEditor-Engine.dll!AGameStateBase::HandleBeginPlay() Line 205|C++|
| |UnrealEditor-Engine.dll!UWorld::BeginPlay() Line 4908|C++|
| |UnrealEditor-Engine.dll!UGameInstance::StartPlayInEditorGameInstance(ULocalPlayer * LocalPlayer, const FGameInstancePIEParameters & Params) Line 500|C++|
| |UnrealEditor-UnrealEd.dll!UEditorEngine::CreateInnerProcessPIEGameInstance(FRequestPlaySessionParams & InParams, const FGameInstancePIEParameters & InPIEParameters, int InPIEInstanceIndex) Line 3052|C++|
| |UnrealEditor-UnrealEd.dll!UEditorEngine::OnLoginPIEComplete_Deferred(int LocalUserNum, bool bWasSuccessful, FString ErrorString, FPieLoginStruct DataStruct) Line 1568|C++|
| |UnrealEditor-UnrealEd.dll!UEditorEngine::CreateNewPlayInEditorInstance(FRequestPlaySessionParams & InRequestParams, const bool bInDedicatedInstance, const EPlayNetMode InNetMode) Line 1830|C++|
| |UnrealEditor-UnrealEd.dll!UEditorEngine::StartPlayInEditorSession(FRequestPlaySessionParams & InRequestParams) Line 2798|C++|
| |UnrealEditor-UnrealEd.dll!UEditorEngine::StartQueuedPlaySessionRequestImpl() Line 1148|C++|
| |UnrealEditor-UnrealEd.dll!UEditorEngine::StartQueuedPlaySessionRequest() Line 1051|C++|
| |UnrealEditor-UnrealEd.dll!UEditorEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 1665|C++|
| |UnrealEditor-UnrealEd.dll!UUnrealEdEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 474|C++|
| |UnrealEditor.exe!FEngineLoop::Tick() Line 5215|C++|
| |[Inline Frame] UnrealEditor.exe!EngineTick() Line 62|C++|
| |UnrealEditor.exe!GuardedMain(const wchar_t * CmdLine) Line 183|C++|
| |UnrealEditor.exe!LaunchWindowsStartup(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * __formal, int nCmdShow, const wchar_t * CmdLine) Line 272|C++|
| |UnrealEditor.exe!WinMain(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * pCmdLine, int nCmdShow) Line 330|C++|
| |[External Code]||

Sleep(1) pauses the whole BeginPlay() and does not change order of logging even if the first thread is paused first.

Ok so here’s some more info to digest:
First I changed the call to Run() to execute in the Tick() function. Then I put the first thread to sleep for 20 seconds. Lastly I used an AddOnScreenDebugMessage() to BeginPlay()
Results:
The whole game is paused until Sleep(20) has run it’s course. The debug message in BeginPlay() doesn’t display until the sleep has run it’s course and the threads are still running first to last.

Is there nothing in between BeginPlay and Run in the stack trace or did you omit it when you copied it?

Anyway, as you can see the Run function is not running in its own thread, it is running in the game thread. Use the debugger to step from a breakpoint on BeginPlay to the entry of the Run method. You will see the codepath that is causing it to get called. It will likely provide clues as to why it isnt running in its own thread.

No nothing. What should I be looking for in the codepath?

How it gets from BeginPlay to Run. For some reason it is running the thread on the game thread and not creating a new thread. Perhaps there are clues as to why on that codepath. Read the code and figure out what its doing and why.

I found an excellent tutorial on MultiThreading in UE4. Still works in UE5. Here is the link:
UE4 Multithreading

1 Like

I’ve now used unreal threads, and unfortunately I can’t reproduce your problem. For me inside Run, IsInGameThread() returns false. Can you check again and confirm that it returns true for you? I think it should return false. You can also print out FPlatformTLS::GetCurrentThreadId() to get the thread id number.

Also I will mention that it is unusual to create MyThread as a local variable:

MyThread t;

Normally you would create it with new and delete and assign it to a member variable:

class MyGameMode {
    /*...*/
    MyThread* Thread;
}

MyGameMode::SomeFunction() {
    Thread = new MyThread(...);
}

(actually I use TUniquePtr so it automatically gets deleted in the destructor)

But I don’t see how that would explain your problem.

Note that the destructor will call Thread->Kill so you shouldn’t be using a local variable as it will likely kill the thread before its finished, which isn’t what you want.

One other thought is that you know you shouldn’t be calling Run yourself right? Run is called for you by the FRunnableThread from the new thread. You were talking about changing where Run was called or something. You shouldn’t be calling Run. That would also explain the reason why IsInGameThread is returning true for you.

Also, Init should return true and not false.

Ahh, so I see what’s going on now. haha.

Change MyThread::Init to return true and remove the call to Run from your code, and it will work.

It’s worth mentioning that FRunnable is useful if you need to communicate with thread while he’s still running.

Most of the time when we just want to init->compute->get result it is more appropriate to use FAsyncTask.

Ran into this while studying multithreading and the issue here is manually calling the “run” function.

I’ve written an article explaining the workflow and what you SHOULD call manually and what you should leave for the engine to handle automatically.

Hope this helps anyone who comes by in the future…

1 Like