UE5线程进阶(2-1):枚举类EAsyncExecution,作业类TAsyncRunnable、TAsyncQueuedWork,及全局线程函数 Async(..),及线程调用的 4 种方法总结

(49) 这是 async 函数的第一个参数 :

(50) 老师的举例讲解

(51)记录一些源代码 ,以下这俩全局函数的作用,是在单线程的操作系统上,启动任务函数的执行,并赋值 promise :

cpp 复制代码
/**  对于不支持多线程的操作系统,由本函数完成 promise 的赋值
 * Template for setting a promise value from a callable.
 */
template<typename ResultType, typename CallableType>
inline void SetPromise(TPromise<ResultType>& Promise, CallableType&& Callable)
{
	Promise.SetValue(Forward<CallableType>(Callable)());
}

template<typename CallableType>
inline void SetPromise(TPromise<void>& Promise, CallableType&& Callable)
{
	Forward<CallableType>(Callable)();
	Promise.SetValue();
}

(52) 以下这个枚举类,也要用到,作为类 TAsyncGraphTask 的数据成员

cpp 复制代码
namespace ENamedThreads
{
	enum Type : int32
	{
		UnusedAnchor = -1,
		/** The always-present, named threads are listed next **/
		RHIThread,
		GameThread,
		// The render thread is sometimes the game thread and is sometimes the actual rendering thread
		ActualRenderingThread = GameThread + 1,
		// CAUTION ThreadedRenderingThread must be the last named thread, insert new named threads before it

		/** not actually a thread index. Means "Unknown Thread" or "Any Unnamed Thread" **/
		AnyThread = 0xff, 

		/** High bits are used for a queue index and priority**/

		MainQueue =			0x000,
		LocalQueue =		0x100,

		NumQueues =			2,
		ThreadIndexMask =	0xff,
		QueueIndexMask =	0x100,
		QueueIndexShift =	8,

		/** High bits are used for a queue index task priority and thread priority**/

		NormalTaskPriority =	0x000,
		HighTaskPriority =		0x200,

		NumTaskPriorities =		2,
		TaskPriorityMask =		0x200,
		TaskPriorityShift =		9,

		NormalThreadPriority = 0x000,
		HighThreadPriority = 0x400,
		BackgroundThreadPriority = 0x800,

		NumThreadPriorities = 3,
		ThreadPriorityMask = 0xC00,
		ThreadPriorityShift = 10,

		/** Combinations **/
		GameThread_Local = GameThread | LocalQueue,
		ActualRenderingThread_Local = ActualRenderingThread | LocalQueue,

		AnyHiPriThreadNormalTask = AnyThread | HighThreadPriority | NormalTaskPriority,
		AnyHiPriThreadHiPriTask = AnyThread | HighThreadPriority | HighTaskPriority,

		AnyNormalThreadNormalTask = AnyThread | NormalThreadPriority | NormalTaskPriority,
		AnyNormalThreadHiPriTask = AnyThread | NormalThreadPriority | HighTaskPriority,

		AnyBackgroundThreadNormalTask = AnyThread | BackgroundThreadPriority | NormalTaskPriority,
		AnyBackgroundHiPriTask = AnyThread | BackgroundThreadPriority | HighTaskPriority,
	}; // 完结 enum Type : int32
	//.......
};

(53) 这些类都与 async 函数在同一个头文件,关系应该有关联 :

++ 给出 runnable 类的 Run ()函数的定义

++ 补充

(54) 本 sync 函数的带注释的源码

cpp 复制代码
/**
 * Execute a given function asynchronously.
 *
 * Usage examples:
 *
 *	// using global function
 *		int TestFunc()
 *		{
 *			return 123;
 *		}
 *
 *		TUniqueFunction<int()> Task = TestFunc();
 *		auto Result = Async(EAsyncExecution::Thread, Task);
 *
 *	// using lambda
 *		TUniqueFunction<int()> Task = []()
 *		{
 *			return 123;
 *		}
 *
 *		auto Result = Async(EAsyncExecution::Thread, Task);
 *
 *
 *	// using inline lambda
 *		auto Result = Async(EAsyncExecution::Thread, []() {
 *			return 123;
 *		}
 *
 * @param CallableType         The type of callable object.  本模板函数的模板参数
 * @param Execution            The execution method to use, i.e. on Task Graph or in a separate thread.
 * @param Function             The function to execute.
 * @param CompletionCallback   An optional callback function that is executed when the function completed execution.
 * @return A TFuture object that will receive the return value from the function.
 */
template<typename CallableType>
auto Async(EAsyncExecution Execution, CallableType&& Callable, TUniqueFunction<void()> CompletionCallback = nullptr) 
-> TFuture<decltype(Forward<CallableType>(Callable)())>   // 可见,本函数的返回值是 TFuture<U> 类型
{
	using ResultType = decltype(Forward<CallableType>(Callable)());            // 函数的返回值
	TUniqueFunction<ResultType()> Function(Forward<CallableType>(Callable));   // 要在线程里执行的函数
	TPromise<ResultType>          Promise(MoveTemp(CompletionCallback));       // promise,并带上了回调函数
	TFuture<ResultType>           Future = Promise.GetFuture();                // 这就是本函数的返回值

	switch (Execution)
	{
	case EAsyncExecution::TaskGraphMainThread:
		// fallthrough
	case EAsyncExecution::TaskGraph:
		{
			TGraphTask<TAsyncGraphTask<ResultType>>::CreateTask().ConstructAndDispatchWhenReady(
				MoveTemp(Function), MoveTemp(Promise), 
				Execution == EAsyncExecution::TaskGraph ? ENamedThreads::AnyThread : ENamedThreads::GameThread);
		}
		break;
	
	case EAsyncExecution::Thread:
		if (FPlatformProcess::SupportsMultithreading())  // 操作系统支持多线程
		{
			TPromise<FRunnableThread*> ThreadPromise;  // 本 TPromise 里封装了运行函数的线程的内存地址

			TAsyncRunnable<ResultType>* Runnable = new TAsyncRunnable<ResultType>(
					MoveTemp(Function), MoveTemp(Promise), ThreadPromise.GetFuture());
		//  TAsyncRunnable(TUniqueFunction<ResultType()>&& InFunction, TPromise<ResultType>&& InPromise, 
		//					TFuture<FRunnableThread*>&& InThreadFuture)

			const FString TAsyncThreadName = FString::Printf(TEXT("TAsync %d"), FAsyncThreadIndex::GetNext()); 
			// 给线程生成一个不会重复的名字

			FRunnableThread* RunnableThread = FRunnableThread::Create(Runnable, *TAsyncThreadName);  
			// 创建线程,任务函数要被执行了

			check(RunnableThread != nullptr);  // enum class FRunnableThread::ThreadType { Real, Fake, Forkable }
			check(RunnableThread->GetThreadType() == FRunnableThread::ThreadType::Real);

			ThreadPromise.SetValue(RunnableThread);  
			// 给这个 promise 填写上面生成的新线程的地址。这是 TAsyncRunnable 的需要的参数
		}
		else
		{
			SetPromise(Promise, Function);
		}
		break;

	case EAsyncExecution::ThreadIfForkSafe:
		if (FPlatformProcess::SupportsMultithreading() || FForkProcessHelper::IsForkedMultithreadInstance())
		{
			TPromise<FRunnableThread*> ThreadPromise;
			TAsyncRunnable<ResultType>* Runnable = new TAsyncRunnable<ResultType>(MoveTemp(Function), MoveTemp(Promise), ThreadPromise.GetFuture());

			const FString TAsyncThreadName = FString::Printf(TEXT("TAsync %d"), FAsyncThreadIndex::GetNext());
			FRunnableThread* RunnableThread = FForkProcessHelper::CreateForkableThread(Runnable, *TAsyncThreadName);

			check(RunnableThread != nullptr);
			check(RunnableThread->GetThreadType() == FRunnableThread::ThreadType::Real);

			ThreadPromise.SetValue(RunnableThread);
		}
		else
		{
			SetPromise(Promise, Function);
		}
		break;

	case EAsyncExecution::ThreadPool:  // 看来是交给全局线程池 GThreadPool 来执行本作业
		if (FPlatformProcess::SupportsMultithreading())
		{
			check(GThreadPool != nullptr);
			GThreadPool->AddQueuedWork(new TAsyncQueuedWork<ResultType>(MoveTemp(Function), MoveTemp(Promise)));
		}
		else
		{
			SetPromise(Promise, Function);
		}
		break;

#if WITH_EDITOR
	case EAsyncExecution::LargeThreadPool:
		if (FPlatformProcess::SupportsMultithreading())
		{
			check(GLargeThreadPool != nullptr);
			GLargeThreadPool->AddQueuedWork(new TAsyncQueuedWork<ResultType>(MoveTemp(Function), MoveTemp(Promise)));
		}
		else
		{
			SetPromise(Promise, Function);
		}
		break;
#endif

	default:
		check(false); // not implemented yet!
	}

	return MoveTemp(Future); // TFuture(TFuture&&) = default; 移动构造函数,本 async 函数执行完毕后,未必拿到了函数结果
}   // future.get() 会触发线程的睡眠等待。本函数只是触发了新线程的创建与执行。

(55)这里总结下 UE 里创建多线程的,目前学到的 4 种方法

a- 继承 FRunnable 接口,再创建 FRunnableThread 的子类对象运行 FRunnable 中的 Run ( ... ) 函数;

b- 使用线程池 FQueuedThreadPool 创建线程,继承自 IQueuedWork 定义作业,并添加给线程池执行;

c- 不再显式使用线程,自定义作业 FMySimpleWork, 定义其中的 4 个函数,

作为 FAsyncTask 或 FAutoDeleteAsyncTask 的模板参数;

再用成员函数 StartBackgroundTask(...) 等启动作业的执行。

d- 使用 全局 Async(...) 函数,定义自己要执行的函数即可,还可以有要拿到的返回值。

(56)全局 Async(...) 函数 的简化版定义

(57)本函数的使用举例

++ 演示回调函数

(58)

谢谢

相关推荐
zhangzhangkeji1 天前
UE5线程进阶(1):
ue5
yblackd4 天前
UnrealEngine Win风格 窗口选择打开文件
c++·ue5·虚幻
AI视觉网奇5 天前
ue 推送直播流 推流 linux 实战
笔记·学习·ue5
郁闷的网纹蟒5 天前
虚幻5---第16部分---敌人(中)
开发语言·c++·ue5·游戏引擎·虚幻
爱搞虚幻的阿恺5 天前
Niagara粒子系统-超炫酷的闪电特效(第一期 粒子的朝向与对齐)
游戏·ue5·游戏引擎·虚幻
暮志未晚Webgl5 天前
UE5实现游戏中英文切换的本地化功能
游戏·ue5
郁闷的网纹蟒5 天前
虚幻5---第15部分---宝藏(掉落物)
开发语言·c++·ue5·游戏引擎·虚幻
HAPPY酷5 天前
C++ 音视频项目与 UE5 渲染与电影制作的关系
c++·ue5·音视频
AI视觉网奇7 天前
3d 数字人 ue metahuman 换脸 换身体
笔记·学习·ue5