目录
在UE4中,应用开发者可以使用的多线程任务一般有三种形式:
1、FRunnableThread + FRunnable
2、AsynTask
3、TaskGraph
下面就一一进行说明。
一、FRunnableThread + FRunnable
这是一种单独创建一个线程,并让任务在此线程上单独执行的一种方式。
FRunnable的部分源代码如下,存放路径:\Engine\Source\Runtime\Core\Public\HAL\Runnable.h
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() { }
};
FRunnableThread的部分源代码如下,存放路径:Engine\Source\Runtime\Core\Public\HAL\RunnableThread.h
class CORE_API FRunnableThread
{
public:
/** Gets a new Tls slot for storing the runnable thread pointer. */
static uint32 GetTlsSlot();
/**
* Factory method to create a thread with the specified stack size and thread priority.
*
* @param InRunnable The runnable object to execute
* @param ThreadName Name of the thread
* @param InStackSize The size of the stack to create. 0 means use the current thread's stack size
* @param InThreadPri Tells the thread whether it needs to adjust its priority or not. Defaults to normal priority
* @return The newly created thread or nullptr if it failed
*/
static FRunnableThread* Create(
class FRunnable* InRunnable,
const TCHAR* ThreadName,
uint32 InStackSize = 0,
EThreadPriority InThreadPri = TPri_Normal,
uint64 InThreadAffinityMask = FPlatformAffinity::GetNoAffinityMask() );
};
开发者需要提供FRunnable的实现,然后调用FRunnableThread::Create(...)方法,这样会创建新的线程,FRunnable的实现类将在此新的线程上执行。
二、AsynTask
此种方式可以使用系统自带的线程池,也可以使用自己创建的线程池,任务由开发者创建。
AsynTask有两种:FAutoDeleteAsyncTask和FAsyncTask,它们的区别在于,前一种当任务结束后会自动删除,后一种任务结束后需要手动删除任务。
FAutoDeleteAsyncTask定义在Engine\Source\Runtime\Core\Public\Async\AsyncWork.h客户,的部分源代码如下:
template<typename TTask>
class FAutoDeleteAsyncTask : private IQueuedWork
{
public:
/* Generic start function, not called directly
* @param bForceSynchronous if true, this job will be started synchronously, now, on this thread
**/
void Start(bool bForceSynchronous)
{
FPlatformMisc::MemoryBarrier();
FQueuedThreadPool* QueuedPool = GThreadPool;
if (bForceSynchronous)
{
QueuedPool = 0;
}
if (QueuedPool)
{
QueuedPool->AddQueuedWork(this);
}
else
{
// we aren't doing async stuff
DoWork();
}
}
/**
* Run this task on this thread, now. Will end up destroying myself, so it is not safe to use this object after this call.
**/
void StartSynchronousTask()
{
Start(true);
}
/**
* Run this task on the lo priority thread pool. It is not safe to use this object after this call.
**/
void StartBackgroundTask()
{
Start(false);
}
};
FAsyncTask定义在Engine\Source\Runtime\Core\Public\Async\AsyncWork.h客户,的部分源代码如下:
template<typename TTask>
class FAsyncTask : private IQueuedWork
{
/* Generic start function, not called directly
* @param bForceSynchronous if true, this job will be started synchronously, now, on this thread
**/
void Start(bool bForceSynchronous, FQueuedThreadPool* InQueuedPool)
{
FScopeCycleCounter Scope( Task.GetStatId(), true );
DECLARE_SCOPE_CYCLE_COUNTER( TEXT( "FAsyncTask::Start" ), STAT_FAsyncTask_Start, STATGROUP_ThreadPoolAsyncTasks );
FPlatformMisc::MemoryBarrier();
CheckIdle(); // can't start a job twice without it being completed first
WorkNotFinishedCounter.Increment();
QueuedPool = InQueuedPool;
if (bForceSynchronous)
{
QueuedPool = 0;
}
if (QueuedPool)
{
if (!DoneEvent)
{
DoneEvent = FPlatformProcess::GetSynchEventFromPool(true);
}
DoneEvent->Reset();
QueuedPool->AddQueuedWork(this);
}
else
{
// we aren't doing async stuff
DestroyEvent();
DoWork();
}
}
/**
* Run this task on this thread
* @param bDoNow if true then do the job now instead of at EnsureCompletion
**/
void StartSynchronousTask()
{
Start(true, GThreadPool);
}
/**
* Queue this task for processing by the background thread pool
**/
void StartBackgroundTask(FQueuedThreadPool* InQueuedPool = GThreadPool)
{
Start(false, InQueuedPool);
}
}
FAutoDeleteAsyncTask和FAsyncTask都可以使用系统默认的FQueuedThreadPool,也可以使用开发者创建的FQueuedThreadPool。
其中TTask的一般形式如下:
class FGenericTask : public FNonAbandonableTask
{
friend class FAutoDeleteAsyncTask<FGenericTask>;
// or friend class FAsyncTask<FGenericTask>;
public:
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FGenericTask, STATGROUP_TaskGraphTasks);
}
void DoWork()
{
}
};
FAutoDeleteAsyncTask和FAsyncTask的例子分别如下:
// start an example job
(new FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>(5))->StartBackgroundTask();
// do an example job now, on this thread
(new FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>(5))->StartSynchronousTask();
FAsyncTask<ExampleAsyncTask>* MyTask = new FAsyncTask<ExampleAsyncTask>( 5 );
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 MyTask;
三、TaskGraph
TaskGraph是最复杂的多线程任务形式,它除了执行开发者提交的任务外,还可以建立任务与任务之间的先后关系。
TaskGraph定义在Engine\Source\Runtime\Core\Public\Async\TaskGraphInterfaces.h中,部分源代码如下:
template<typename TTask>
class TGraphTask : public FBaseGraphTask
{
/**
* This is a helper class returned from the factory. It constructs the embeded task with a set of arguments and sets the task up and makes it ready to execute.
* The task may complete before these routines even return.
**/
class FConstructor
{
public:
/** Passthrough internal task constructor and dispatch. Note! Generally speaking references will not pass through; use pointers */
template<typename...T>
FGraphEventRef ConstructAndDispatchWhenReady(T&&... Args)
{
new ((void *)&Owner->TaskStorage) TTask(Forward<T>(Args)...);
return Owner->Setup(Prerequisites, CurrentThreadIfKnown);
}
/** Passthrough internal task constructor and hold. */
template<typename...T>
TGraphTask* ConstructAndHold(T&&... Args)
{
new ((void *)&Owner->TaskStorage) TTask(Forward<T>(Args)...);
return Owner->Hold(Prerequisites, CurrentThreadIfKnown);
}
}
/**
* Factory to create a task and return the helper object to construct the embedded task and set it up for execution.
* @param Prerequisites; the list of FGraphEvents that must be completed prior to this task executing.
* @param CurrentThreadIfKnown; provides the index of the thread we are running on. Can be ENamedThreads::AnyThread if the current thread is unknown.
* @return a temporary helper class which can be used to complete the process.
**/
static FConstructor CreateTask(const FGraphEventArray* Prerequisites = NULL, ENamedThreads::Type CurrentThreadIfKnown = ENamedThreads::AnyThread)
{
int32 NumPrereq = Prerequisites ? Prerequisites->Num() : 0;
if (sizeof(TGraphTask) <= FBaseGraphTask::SMALL_TASK_SIZE)
{
void *Mem = FBaseGraphTask::GetSmallTaskAllocator().Allocate();
return FConstructor(new (Mem) TGraphTask(TTask::GetSubsequentsMode() == ESubsequentsMode::FireAndForget ? NULL : FGraphEvent::CreateGraphEvent(), NumPrereq), Prerequisites, CurrentThreadIfKnown);
}
return FConstructor(new TGraphTask(TTask::GetSubsequentsMode() == ESubsequentsMode::FireAndForget ? NULL : FGraphEvent::CreateGraphEvent(), NumPrereq), Prerequisites, CurrentThreadIfKnown);
}
}
实现类为FTaskGraphImplementation,它内部有一个线程池,其中前4个线程名分别为:RHIThread、 AudioThread、GameThread、ActualRenderingThread,它们不是由FTaskGraphImplementation内部创建的。而除此以外的其它线程,都是在FTaskGraphImplementation内部创建的,名字都以“TaskGraphThread”开关。
FTaskGraphImplementation定义在Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp中
其中TTask的一般形式为:
#pragma once
#include "CoreTypes.h"
#include "Async/TaskGraphInterfaces.h"
class TTaskTemplate
{
public:
FORCEINLINE static TStatId GetStatId()
{
RETURN_QUICK_DECLARE_CYCLE_STAT(TTaskTemplate, STATGROUP_TaskGraphTasks);
};
// RHIThread, AudioThread, GameThread, ActualRenderingThread
static ENamedThreads::Type GetDesiredThread()
{
return ENamedThreads::AnyThread;
};
// TrackSubsequents or FireAndForget
static ESubsequentsMode::Type GetSubsequentsMode()
{
return ESubsequentsMode::TrackSubsequents;
};
TTaskTemplate()
{
};
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
};
};
TTask可以参考的例子:FTickFunctionTask, FReturnGraphTask
若是执行一个单独的任务,则第一个参数设置为NULL,代码如下:
FGraphEventRef Join=TGraphTask<TTaskTemplate>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady();
若是执行有依赖关系的任务,则第一个参数要传入它所依赖的一个或多个任务的FGraphEventRef
FGraphEventArray* Prerequisites = new FGraphEventArray();
Prerequisites->Add(Join);
FGraphEventRef Join=TGraphTask<FVictoryTestTask>::CreateTask(Prerequisites, ENamedThreads::GameThread).ConstructAndDispatchWhenReady();
参考文档
MultiThreading and synchronization Guide
Multi-Threading: Task Graph System
《Exploring in UE4》多线程机制详解[原理分析]