Unreal Engine 4:学习笔记(十五)MultiThreading Task

目录

一、FRunnableThread + FRunnable

二、AsynTask

三、TaskGraph


在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
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
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;
// or friend class FAsyncTask;
public: 
	FORCEINLINE TStatId GetStatId() const
	{
		RETURN_QUICK_DECLARE_CYCLE_STAT(FGenericTask, STATGROUP_TaskGraphTasks);
	}
	void DoWork() 
	{ 
	} 
};

FAutoDeleteAsyncTask和FAsyncTask的例子分别如下:

// start an example job
(new FAutoDeleteAsyncTask(5))->StartBackgroundTask();

// do an example job now, on this thread
(new FAutoDeleteAsyncTask(5))->StartSynchronousTask();
FAsyncTask* MyTask = new FAsyncTask( 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
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
		FGraphEventRef ConstructAndDispatchWhenReady(T&&... Args)
		{
			new ((void *)&Owner->TaskStorage) TTask(Forward(Args)...);
			return Owner->Setup(Prerequisites, CurrentThreadIfKnown);
		}

		/** Passthrough internal task constructor and hold. */
		template
		TGraphTask* ConstructAndHold(T&&... Args)
		{
			new ((void *)&Owner->TaskStorage) TTask(Forward(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::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady();

若是执行有依赖关系的任务,则第一个参数要传入它所依赖的一个或多个任务的FGraphEventRef

FGraphEventArray* Prerequisites = new FGraphEventArray();
Prerequisites->Add(Join);
FGraphEventRef Join=TGraphTask::CreateTask(Prerequisites, ENamedThreads::GameThread).ConstructAndDispatchWhenReady();

参考文档

MultiThreading and synchronization Guide
Multi-Threading: Task Graph System
《Exploring in UE4》多线程机制详解[原理分析]

你可能感兴趣的:(C++,Unreal,Engine,4,multi-threading,task)