UE4 多线程使用分为以下几种:
1.创建标准线程
2.使用线程池
3.使用TaskGraph系统
使用线程时需要注意,不要在非GameThread线程内执行下面几个操作:
-
不要 创建/ 修改/ 删除 任何 派生自UObject的对象。
-
不要使用定时器 TimerManager
-
不要使用任何绘制接口,例如 DrawDebugLine
1.创建标准线程
创建标准线程的使用方法有点类似java中创建线程的方法,主要有以下两个类:
FRunnable:线程执行类的基类
FRunnableThread:线程类,根据平台不同会通过静态方法Create创建不同的线程类,比如FRunnableThreadWin, FRunnableThreadAndroid等等。
使用方法:
先派生一个FRunnable的执行类,并创建类对象,
然后使用FRunnableThread::Create创建线程并将对象传入。
通常做法:创建FRunnable派生类对象并创建线程的过程放在FRunnable派生类的构造函数中。
FRunnable派生类必须重写基类的Run方法。下面是FRunnable类的格式
class CORE_API FRunnable
{
public:
//初始化FRunable对象,该方法在新线程(执行Run的线程)中被执行,而不是在创建FRunable的线程中执行,返回true 表示初始化成功,将执行FRunable的Run方法,否则FRunnableThread中的线程将立即结束,Run不会执行
virtual bool Init()。
{
return true;
}
//该方法只有在Init()返回true 时,才执行,是线程中具体执行的任务。
virtual uint32 Run() = 0;
//用于提供提前停止线程的执行的条件。通常会设置一个停止标志,然后在Run中检测这个标志,检测后停止线程。经常与FRunnableThread的WaitForCompletion方法一起使用。WaitForCompletion方法会阻塞直到线程退出。
//Stop方法在FRunnableThread的kill方法中被调用(kill可以选择等待还是不等待线程结束,kill实际也是通过stop来停止线程,如果stop没有执行停止线程的操作,则skill如果是等待线程,则将一致阻塞),而kill方法也会被FRunnableThread::~FRunnableThread()自动调用,这时kill是等待线程结束的
virtual void Stop() { }
//该方法在Run结束后执行,该方法只有在Init()返回true 时,才执行,该方法在执行Run的线程中被执行,而不是在创建FRunable的线程中执行
virtual void Exit() { }
...
};
具体用法如下:
class UZMQServerThread : public FRunnable
{
public:
UZMQServerThread();
~UZMQServerThread();
virtual bool Init() override;
virtual uint32 Run() override;
virtual void Stop()override;
virtual void Exit() override;
private:
//这里用FThreadSafeCounter应该更好
bool bStop;
FRunnableThread* Thread;
};
UZMQServerThread::UZMQServerThread() {
//这里创建线程对象并传入FRunnable对象和线程的名称,还可以设置线程优先级,默认是TPri_Normal。
Thread = FRunnableThread::Create(this, TEXT("ZMQBodyThread"));
}
void UZMQServerThread::Stop() {
bStop = true;
}
void UZMQServerThread::Exit() {
}
UZMQServerThread::~UZMQServerThread()
{
if (Thread) {
//这里会自动调用FRunnableThread的kill方法,FRunnableThread的kill会调用UZMQServerThread::Stop,并阻塞直到线程停止。
delete Thread;
Thread = nullptr;
}
}
bool UZMQServerThread::Init()
{
bool ret = true;
//这里是初始化代码,返回值决定是否执行Run的
return ret;
}
uint32 UZMQServerThread::Run()
{
while (!bStop)
{
//这里是你要执行的代码
}
return 0;
}
2.使用线程池中的线程。
使用线程池通常是通过FAsyncTask和FAutoDeleteAsyncTask来使用的,这两个类都是所谓的异步任务。 异步任务可以用于执行一直运行的异步处理(比如while(1)),也可以用于执行时间较长的异步操作。
使用FAsyncTask需要手动删除,FAutoDeleteAsyncTask会在任务完成时自动删除。
使用异步任务,需要先定义一个异步任务的执行类型,执行类型应该派生自FNonAbandonableTask,具体形式如下
class ExampleAsyncTask : public FNonAbandonableTask
{
friend class FAsyncTask<ExampleAsyncTask>;
int32 ExampleData;
ExampleAsyncTask(int32 InExampleData)
: ExampleData(InExampleData)
{
}
void DoWork()
{
... do the work here
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(ExampleAsyncTask, STATGROUP_ThreadPoolAsyncTasks);
}
};
具体使用异步任务的方法为:
void Example()
{
//start an example job
FAsyncTask<ExampleAsyncTask>* MyTask = new FAsyncTask<ExampleAsyncTask>(5)//这里的5是传给ExampleAsyncTask构造函数的参数,他的构造函数在当前线程执行;
MyTask->StartBackgroundTask();//执行异步任务
//--or --
MyTask->StartSynchronousTask();//执行同步任务,既任务在该条语句堵塞执行,直到完成
//to just do it now on this thread
//Check if the task is done :
if (MyTask->IsDone())//判断是否完成,不会堵塞
{
}
//Spinning on IsDone is not acceptable( see EnsureCompletion ), but it is ok to check once a frame.
//Ensure the task is done, doing the task on the current thread if it has not been started, waiting until completion in all cases.
MyTask->EnsureCompletion(); //该函数检测完成,如果未完成,将等待直到完成,将导致当前线程堵塞
delete Task;
}
3.使用TaskGraph系统
Task Graph 系统是UE4一套抽象的异步任务处理系统,可以创建多个多线程任务,指定各个任务之间的依赖关系,按照该关系来依次处理任务。
在引擎初始化FTaskGraphImplementation的时候,我们就会默认构建24个FWorkerThread工作线程(这里支持最大的线程数量也就是24),其中里面有5个是默认带名字的线程,StatThread、RHIThread、AudioThread(声音线程)、GameThread(主线程,游戏线程)、ActualRenderingThread(渲染线程),还有前面提到的N个非指定名称的任意线程,这个N由CPU核数决定。对于带有名字的线程,他不需要创建新的Runnable线程,因为他们会在其他的时机创建,如StatThread以及RenderingThread会在FEngineLoop.PreInit里创建。
taskGraph系统适用于那些比较小的异步处理,大型的异步处理任务,应该使用创建标准线程FRunnable和使用FAsyncTask(尤其不能在GameTread里放置耗时的任务,甚至是while(1),这将严重影响帧率)。
使用TaskGraph,首先要创建TaskGraph所需的任务类型,任务类型创建如下(有点类似FAsyncTask的实际执行类型):
class FGenericTask
{
TSomeType SomeArgument;
public:
FGenericTask(TSomeType InSomeArgument) // CAUTION!: Must not use references in the constructor args; use pointers instead if you need by reference
: SomeArgument(InSomeArgument)
{
// Usually the constructor doesn't do anything except save the arguments for use in DoWork or GetDesiredThread.
}
~FGenericTask()
{
// you will be destroyed immediately after you execute. Might as well do cleanup in DoWork, but you could also use a destructor.
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FGenericTask, STATGROUP_TaskGraphTasks);
}
static ENamedThreads::Type GetDesiredThread()//返回所需要运行在的线程
{
return ENamedThreads::[named thread or AnyThread];
}
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)//实际执行的任务
{
// The arguments are useful for setting up other tasks.
// Do work here, probably using SomeArgument.
MyCompletionGraphEvent->DontCompleteUntil(TGraphTask<FSomeChildTask>::CreateTask(NULL, CurrentThread).ConstructAndDispatchWhenReady());
}
};
具体使用如下:
FGraphEventRef Join = TGraphTask<FGenericTask>::CreateTask().ConstructAndDispatchWhenReady(SomeArgument);
//下面的语句是用来检测任务是否完成,不堵塞
if(Join->IsComplete()){....}
//下面的语句将阻塞当前线程等待任务完成
FTaskGraphInterface::Get().WaitUntilTaskCompletes(Join);
4.使用快捷创建函数使用以上三种线程方式
快捷创建函数有两个,一个是Async,一个是AsyncTask。
Async函数: 多线程任务执行。
TFuture<ResultType> Async(EAsyncExecution Execution, TFunction<ResultType()> Function, TFunction<void()> CompletionCallback = TFunction<void()>())
enum class EAsyncExecution
{
/** Execute in Task Graph (for short running tasks). */
TaskGraph,//这个是使用taskGraph,线程类型为ENamedThreads::AnyThread
/** Execute in separate thread (for long running tasks). */
Thread,//这个是使用做标准的FRunnableThread和FRunnable
/** Execute in global queued thread pool. */
ThreadPool//这个是使用线程池GThreadPool,类似于AsyncTask
};
TFuture,其中ResultType则是传入的执行函数的返回值。
TFunction<int()> My_Task= []() {
return 123;
};
auto Future = Async(EAsyncExecution::TaskGraph, My_Task);
//任务返程后通过下面获得结果
int Result = Future.Get();
AsyncTask函数
其实是使用了taskGraph,可以自己指定线程类型
void AsyncTask(ENamedThreads::Type Thread, TFunction<void()> Function)
{
TGraphTask<FAsyncGraphTask>::CreateTask().ConstructAndDispatchWhenReady(Thread, MoveTemp(Function));
}