目前对于UE4来说,要实现多线程有多种方式,主要分为一下几种:
- FRunnable&&FRunnableThread
- TaskGraph系统
- AsyncTask系统
一、FRunnable&&FRunnableThread
UE4遵循C++11,因此,我们可以使用std::thread实现多线程。然而,UE4为了能够实现简单跨平台,实现了自己的多线程机制,也即是FRunnable,类代码如下所示:
class CORE_API FRunnable
{
public:
/**
* Initializes the runnable object.
*
* This method is called in the context of the thread object that aggregates this, not the
* thread that passes this runnable to a new thread.
*
* @return True if initialization was successful, false otherwise
* @see Run, Stop, Exit
*/
virtual bool Init()
{
return true;
}
/**
* Runs the runnable object.
*
* This is where all per object thread work is done. This is only called if the initialization was successful.
*
* @return The exit code of the runnable object
* @see Init, Stop, Exit
*/
virtual uint32 Run() = 0;
/**
* Stops the runnable object.
*
* This is called if a thread is requested to terminate early.
* @see Init, Run, Exit
*/
virtual void Stop() { }
/**
* Exits the runnable object.
*
* Called in the context of the aggregating thread to perform any cleanup.
* @see Init, Run, Stop
*/
virtual void Exit() { }
/**
* Gets single thread interface pointer used for ticking this runnable when multi-threading is disabled.
* If the interface is not implemented, this runnable will not be ticked when FPlatformProcess::SupportsMultithreading() is false.
*
* @return Pointer to the single thread interface or nullptr if not implemented.
*/
virtual class FSingleThreadRunnable* GetSingleThreadInterface( )
{
return nullptr;
}
/** Virtual destructor */
virtual ~FRunnable() { }
};
是的,源码很少,而且全是接口函数。那么问题来着,FRunable是和std::thread一样的线程吗?
答案当然是否,实际上,在多线程实现的时候,FRunable仅仅是参数,而真正的线程是FRunnableThread。下面关系类图:
基于此,不难理解,FRunnable&&FRunnableThread实现方式为:
- 用户继承FRunnable,重写Init(),Run()等相关函数。
- 利用FRunnableThread::Create()函数,创建“线程”。
- 根据平台API,调用不同的类实现( FPlatformProcess::CreateRunnableThread()),创建真正的线程
具体使用方式
创建基类,继承FRunnable:
UENUM(BlueprintType)
enum class EThreadStatus :uint8
{
New UMETA(DisplayName ="New"),
Runnable UMETA(DisplayName = "Runnable"),
Blocked UMETA(DisplayName = "Blocked"),
Waiting UMETA(DisplayName = "Waiting"),
TimedWaiting UMETA(DisplayName='TimedWaiting'),
Terminated UMETA(DisplayName = 'Terminated')
};
UENUM(BlueprintType)
enum class EPriority :uint8
{
TPri_Normal UMETA(DisplayName = "TPri_Normal"),
TPri_AboveNormal UMETA(DisplayName="TPri_AboveNormal"),
TPri_BelowNormal UMETA(DisplayName = "TPri_BelowNormal"),
TPri_Highest UMETA(DisplayName = "TPri_Highest"),
TPri_Lowest UMETA(DisplayName = "TPri_Lowest"),
TPri_SlightlyBelowNormal UMETA(DisplayName = "TPri_SlightlyBelowNormal"),
TPri_TimeCritical UMETA(DisplayName = "TPri_TimeCritical")
};
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class EASYTHREAD_API UThreadWorkerComponent:public UActorComponent, public FRunnable
{
GENERATED_BODY()
public:
UThreadWorkerComponent();
~UThreadWorkerComponent();
public:
FString ThreadName;
FRunnableThread* Thread;
FThreadSafeCounter StopTaskCounter;
FCriticalSection Mutex;
FEvent* ThreadSuspendedEvent;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EasyThread|Thread")
EThreadStatus Status;
public:
bool Init();
uint32 Run();
void EndPlay(const EEndPlayReason::Type EndPlayReason);
void EnsureCompletion();
UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
void Stop();
UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
void Exit();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "EasyThread|Thread")
bool IsFinished();
virtual bool IsFinished_Implementation();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "EasyThread|Thread")
int32 ThreadBody();
virtual int32 ThreadBody_Implementation();
UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
EThreadStatus GetThreadStatus();
UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
void Suspend();
UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
void Resume();
UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
void TaskSleep(float Seconds);
UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
void CreateThread(const FString InThreadName, const int32 InStackSize, const EPriority InThreadPriority);
UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
bool Lock();
UFUNCTION(BlueprintCallable, Category = "EasyThread|Thread")
void UnLock();
FCriticalSection* GetCriticalSection();
};
代码说明:
- 建议在继承FRunnable同时继承UObject,使用UE4的GC
- 为了能在蓝图以及Actor中直接使用,因此创建同时继承了FRunnable以及UActorComponent
- 为了在编辑器中运行调试,重载EndPlay() -----这也是继承UActorComponent的原因之一,否则在PIE中运行后停止,容易存在线程阻塞。也即是说不能仅仅在析构函数中停止线程,如果要在PIE模式下调试,就需要在EndPlay()中终止线程。
// Copyright 2018 Bruce, Inc. All Rights Reserved
#include "ThreadWorker.h"
#include "Runtime/Core/Public/HAL/RunnableThread.h"
#include "Runtime/Core/Public/Misc/ScopeLock.h"
UThreadWorkerComponent::UThreadWorkerComponent():Status(EThreadStatus::New),StopTaskCounter(0)
{
ThreadSuspendedEvent = FPlatformProcess::GetSynchEventFromPool();
}
UThreadWorkerComponent::~UThreadWorkerComponent()
{
if (Thread)
{
delete Thread;
Thread = nullptr;
}
FPlatformProcess::ReturnSynchEventToPool(ThreadSuspendedEvent);
ThreadSuspendedEvent = nullptr;
}
uint32 UThreadWorkerComponent::Run()
{
Status = EThreadStatus::Runnable;
do
{
ThreadBody();
} while (StopTaskCounter.GetValue() == 0 && !IsFinished());
return 1;
}
void UThreadWorkerComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
//EnsureCompletion();
if (Thread)
{
Thread->Kill(false);
Stop();
}
}
void UThreadWorkerComponent::EnsureCompletion()
{
if (Thread)
{
Thread->Kill(true);
Stop();
Thread->WaitForCompletion();
}
}
void UThreadWorkerComponent::Stop()
{
if (Status != EThreadStatus::Terminated)
{
StopTaskCounter.Increment();
UE_LOG(LogClass, Log, TEXT("---------------Stop---------------"));
Status = EThreadStatus::Terminated;
}
}
void UThreadWorkerComponent::Exit()
{
if (Status != EThreadStatus::Terminated)
{
StopTaskCounter.Increment();
Status = EThreadStatus::Terminated;
}
}
bool UThreadWorkerComponent::IsFinished_Implementation()
{
return true;
}
int32 UThreadWorkerComponent::ThreadBody_Implementation()
{
return 0;
}
EThreadStatus UThreadWorkerComponent::GetThreadStatus()
{
return Status;
}
void UThreadWorkerComponent::Suspend()
{
ThreadSuspendedEvent->Wait();
Status = EThreadStatus::Waiting;
}
void UThreadWorkerComponent::Resume()
{
ThreadSuspendedEvent->Trigger();
Status = EThreadStatus::Runnable;
}
void UThreadWorkerComponent::CreateThread(const FString InThreadName, const int32 InStackSize, const EPriority InThreadPriority)
{
EThreadPriority ThreadPriority = static_cast<EThreadPriority>(static_cast<uint8>(InThreadPriority));
ThreadName = InThreadName;
Thread = FRunnableThread::Create(this, *ThreadName, InStackSize, ThreadPriority, FPlatformAffinity::GetNoAffinityMask());
}
bool UThreadWorkerComponent::Lock()
{
return Mutex.TryLock();
}
void UThreadWorkerComponent::UnLock()
{
Mutex.Unlock();
}
FCriticalSection* UThreadWorkerComponent::GetCriticalSection()
{
return &Mutex;
}
bool UThreadWorkerComponent::Init()
{
UE_LOG(LogClass, Log, TEXT("---------------%S---------------"), *ThreadName);
Status = EThreadStatus::New;
return true;
}
void UThreadWorkerComponent::TaskSleep(float Seconds)
{
FPlatformProcess::Sleep(Seconds);
}
代码说明
- 蓝图通过重写ThreadBody函数,以及IsFinished函数,之后调用CreateThread()便可创建线程。
- 为了使用便利,重写之时可以不在ThreadBody中写循环,而是重写IsFinished函数
- EndPlay()中,使用Kill函数终止线程,传入bShouldWait参数为否,立刻终止线程。此处存在两个问题,若为立即终止线程,若线程中存在死循环亦或阻塞函数,则线程永远不会停止(即使退出编辑器或者程序),该线程会卡死主线程,形成死锁。不过如果立即终止,若ThreadBody内为某个资源上锁,则会形成死锁,资源永远无法释放。在写这篇文章时,笔者暂用强制结束线程的方式,默认在Thread中不会锁住编辑器相关资源。
- 其余函数实现参考WIKI及网上相关教程:
- WIKI教程 By Rama
二、AsyncTask系统
待续