【UE4 C++ 基础知识】<13> 多线程——TaskGraph
概述
- TaskGraph 系统是UE4一套抽象的异步任务处理系统
- TaskGraph 可以看作一种”基于任务的并行编程“设计思想下的实现
- 通过TaskGraph ,可以创建任意多线程任务, 异步任务, 序列任务, 并行任务等,并可以指定任务顺序, 设置任务间的依赖, 最终形成一个任务图, 该系统按照设定好的依赖关系来分配任务图中的任务到各个线程中执行, 最终执行完整个任务图。
- TaskGraph适合简单的任务或者想实现有依赖关系的线程,复杂的任务推荐使用 Runnable 或者 AsynTask
TaskGraph 类定义
模块构成
自定义的任务必须要满足 TGraphTask 中对 Task 的接口需求
构造函数可以传参,最好不要使用引用类型,会有”悬空引用“的风险,可以使用指针来代替引用
GetStatId()
固定写法,函数内传入自定义TaskGraph类型GetDesiredThread()
指定在哪个线程运行ENamedThreads::Type
AnyThread
GameThread
适合访问UObject,可能会阻塞主线程RHIThread
AudioThread
- 也可以通过
FAutoConsoleTaskPriority
对象获取合适的线程
GetSubsequentsMode()
后续执行模式,因为可以有子任务ESubsequentsMode::TrackSubsequents
存在后续任务,实际没有后续任务也不影响,常用该类型ESubsequentsMode::FireAndForget
没有后续任务
DoTask()
线程逻辑执行函数
TaskGraph 简单实现
FTaskGraph_SimpleTask 任务类
class FTaskGraph_SimpleTask
{
FString m_ThreadName; public:
FTaskGraph_SimpleTask(const FString& ThreadName) : m_ThreadName(ThreadName) {}
~FTaskGraph_SimpleTask(){} // 固定写法
FORCEINLINE TStatId GetStatId() const {
RETURN_QUICK_DECLARE_CYCLE_STAT(FTaskGraph_SimpleTask, STATGROUP_TaskGraphTasks);
} // 指定在哪个线程运行
static ENamedThreads::Type GetDesiredThread() { return ENamedThreads::AnyThread; } // 后续执行模式
static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; } // 线程逻辑执行函数
void DoTask(ENamedThreads::TypeCurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
// 逻辑任务
// 可创建 Child Task
UE_LOG(LogTemp, Warning, TEXT("Thread %s Begin!"), *m_ThreadName);
UE_LOG(LogTemp, Warning, TEXT("Thread %s End!"), *m_ThreadName);
}
};
ATaskGraphActor 调用的AActor
UFUNCTION(BlueprintCallable)
void CreateTaskGraph_SimpleTask(const FString& ThreadName);
void ATaskGraphActor::CreateTaskGraph_SimpleTask(const FString& ThreadName)
{
TGraphTask<FTaskGraph_SimpleTask>::CreateTask().ConstructAndDispatchWhenReady(ThreadName); // ThreadName 为 FTaskGraph_SimpleTask 构造函数参数
}
了解任务的创建
在上一小节中,我们可以使用以下代码创建任务
FGraphEventRef GraphEventRef = TGraphTask<FTaskGraph_SimpleTask>::CreateTask().ConstructAndDispatchWhenReady(ThreadName); // 创建任务立即执行
TGraphTask<FTaskGraph_SimpleTask>* GraphTask = TGraphTask<FTaskGraph_SimpleTask>::CreateTask().ConstructAndHold(ThreadName); // 创建任务挂起,等待 unlock() 触发任务执行
TGraphTask<T>
是模板类,可以指定自定义任务的类型CreateTask()
- 第一个参数
Prerequisites
- 用来指定该任务依赖的事件数组,默认为 NULL.
- 在所有依赖事件都触发后,该任务才会放到任务队列里面分配给线程执行。
- 第二个参数
ENamedThreads::Type
- 用来指定线程类型
// 完整函数为
static FConstructor CreateTask(const FGraphEventArray* Prerequisites = NULL, ENamedThreads::Type CurrentThreadIfKnown = ENamedThreads::AnyThread)
- 第一个参数
ConstructAndDispatchWhenReady()
- 创建任务后立即执行
- 调用创建的TaskGraph类型(本例中为FTaskGraph_SimpleTask)的构造函数
- 可以传递构造函数的参数,进行初始化
ConstructAndHold()
- 创建任务后挂起,等待 unlock() 唤醒任务执行
- 参数同上
TaskGraph 并行任务
具有依赖关系的并行任务
- 如图所示,B 依赖于 A0 和 A1,即 A0、A1 执行完毕后 B 才开始执行
- C0 和 C1 依赖于B 的完成
派发任务
- 如图所示,虚线框为派发子任务
- B 的开始依赖于 A0 和 A1 的完成
- B 有两个子任务 B0 和 B1,B 的完成 需要满足 B0 和 B1 也完成
- C0 和 C1 的开始依赖于B 的完成,因而 C0 和 C1 的开始 需要满足 B、B0 和 B1 都完成
C++代码
- TaskGraph_SimpleTask.h
#pragma once
#include "CoreMinimal.h"
#include "TaskGraph_SimpleTask.generated.h"
DECLARE_DELEGATE_OneParam(FGraphTaskDelegate,const FString&); // 单播委托
USTRUCT(BlueprintType)
struct FTaskGraphItem { // 结构体用来传参
GENERATED_USTRUCT_BODY()
public:
UPROPERTY(BlueprintReadWrite)
FString m_ThreadName; // 线程名称
FGraphEventRef m_GraphEventRef; // 自动执行的任务
TGraphTask<class FTaskGraphWithPrerequisitesAndChild>* m_GraphTask; // 需要触发执行的任务
// 构造函数
FTaskGraphItem(FString ThreadName = TEXT("None"), FGraphEventRef GraphEventRef = nullptr, TGraphTask<class FTaskGraphWithPrerequisitesAndChild>* GraphTask = nullptr)
:m_ThreadName(ThreadName), m_GraphEventRef(GraphEventRef), m_GraphTask(GraphTask) {}
~FTaskGraphItem()
{
m_GraphEventRef = nullptr;
m_GraphTask = nullptr;
}
};
class FTaskGraph_SimpleTask // 作为具体执行的任务
{
FString m_ThreadName;
FGraphTaskDelegate m_GraphTaskDelegate;
public:
FTaskGraph_SimpleTask(const FString& ThreadName, FGraphTaskDelegate GraphTaskDelegate)
: m_ThreadName(ThreadName), m_GraphTaskDelegate(GraphTaskDelegate) {}
~FTaskGraph_SimpleTask(){}
// 固定写法
FORCEINLINE TStatId GetStatId() const {
RETURN_QUICK_DECLARE_CYCLE_STAT(FTaskGraph_SimpleTask, STATGROUP_TaskGraphTasks);
}
// 指定在主线程,因为用到 AActor 蓝图里的函数
static ENamedThreads::Type GetDesiredThread() { return ENamedThreads::GameThread; }
// 后续执行模式
static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
// 线程逻辑执行函数
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
check(IsInGameThread()); //确认是否在主线程
FString message = FString::Printf(TEXT("SimpleTaskTask[%s] execute the GraphTaskDelegate!"), *m_ThreadName);
m_GraphTaskDelegate.ExecuteIfBound(message);
//UE_LOG(LogTemp, Warning, TEXT("SimpleTaskTask[%s] execute!"), *m_ThreadName);
}
};
class FTaskGraphWithPrerequisitesAndChild // 作为通用任务,可作为依赖事件的任务,也可作为子任务
{
FString m_ThreadName;
TArray<TGraphTask<FTaskGraphWithPrerequisitesAndChild>*> m_ChildGraphTask; // 子任务数组
FGraphTaskDelegate m_GraphTaskDelegate; // 单播委托
public:
// 构造函数
FTaskGraphWithPrerequisitesAndChild(const FString& ThreadName, const TArray<TGraphTask<FTaskGraphWithPrerequisitesAndChild>*>& ChildTask, FGraphTaskDelegate GraphTaskDelegate)
: m_ThreadName(ThreadName), m_ChildGraphTask(ChildTask), m_GraphTaskDelegate(GraphTaskDelegate) {}
~FTaskGraphWithPrerequisitesAndChild() {}
// 固定写法
FORCEINLINE TStatId GetStatId() const {
RETURN_QUICK_DECLARE_CYCLE_STAT(FTaskGraphWithPrerequisitesAndChild, STATGROUP_TaskGraphTasks);
}
// 指定在哪个线程运行
static ENamedThreads::Type GetDesiredThread() { return ENamedThreads::AnyThread; }
// 后续执行模式
static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
// 线程逻辑执行函数
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
UE_LOG(LogTemp, Warning, TEXT("Task[%s] Begin!"), *m_ThreadName);
// 执行子任务,此处通用任务作为子任务
if (m_ChildGraphTask.Num()>0)
{
for (auto GraphTaskItem : m_ChildGraphTask)
{
GraphTaskItem->Unlock(); // 唤醒子任务
MyCompletionGraphEvent->DontCompleteUntil(GraphTaskItem->GetCompletionEvent());
}
// 如有需要,可设法检测所有子任务是否都完成
}
// 创建并执行子任务,本处作为具体执行的任务
MyCompletionGraphEvent->DontCompleteUntil(TGraphTask<FTaskGraph_SimpleTask>::CreateTask().ConstructAndDispatchWhenReady(m_ThreadName, m_GraphTaskDelegate));
UE_LOG(LogTemp, Warning, TEXT("Task[%s] End!"), *m_ThreadName);
}
};
- TaskGraphActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TaskGraph_SimpleTask.h"
#include "TaskGraphActor.generated.h"
UCLASS()
class TIPS_API ATaskGraphActor : public AActor
{
GENERATED_BODY()
public:
ATaskGraphActor();
protected:
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// 创建任务
UFUNCTION(BlueprintCallable)
FTaskGraphItem CreateGraphTask(const FString& ThreadName, const TArray<FTaskGraphItem>& Prerequisites,const TArray<FTaskGraphItem>& ChildTasks,bool DispatchWhenReady );
// 创建任务,CreateGraphTask 的简化
UFUNCTION(BlueprintCallable)
FTaskGraphItem CreateGraphTaskPure(const FString& ThreadName, bool DispatchWhenReady) {
return CreateGraphTask(ThreadName, TArray<FTaskGraphItem>(), TArray<FTaskGraphItem>(), DispatchWhenReady);
}
// 唤醒挂起的任务
UFUNCTION(BlueprintCallable)
void TriggerGraphTask(FTaskGraphItem TaskGraphItem);
// 用于任务中执行的回调函数
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent)
void OnTaskFinished(const FString& message);
}
- TaskGraphActor.cpp
FTaskGraphItem ATaskGraphActor::CreateGraphTask(const FString& ThreadName, const TArray<FTaskGraphItem>& Prerequisites, const TArray<FTaskGraphItem>& ChildTasks, bool DispatchWhenReady)
{
FGraphEventArray PrerequisiteEvents; // 依赖事件
TArray<TGraphTask<FTaskGraphWithPrerequisitesAndChild>*> ChildGraphTask; // 子任务
UE_LOG(LogTemp, Warning, TEXT("Task[%s] is Created!"), *ThreadName);
if (Prerequisites.Num()>0)
{
for (FTaskGraphItem item : Prerequisites) // 结构体数组提取依赖事件
{
if (item.m_GraphEventRef)
{
PrerequisiteEvents.Add(item.m_GraphEventRef);
UE_LOG(LogTemp, Warning, TEXT("Task[%s] wait Task[%s]!"), *ThreadName,*item.m_ThreadName);
}
else if (item.m_GraphTask)
{
PrerequisiteEvents.Add(item.m_GraphTask->GetCompletionEvent());
UE_LOG(LogTemp, Warning, TEXT("Task[%s] wait Task[%s]!"), *ThreadName,*item.m_ThreadName);
}
}
}
if (ChildTasks.Num()>0)
{
for (FTaskGraphItem item : ChildTasks) // 提取子任务
{
if (item.m_GraphTask)
{
ChildGraphTask.Add(item.m_GraphTask);
UE_LOG(LogTemp, Warning, TEXT("Task[%s] is Task[%s] child task!"), *item.m_ThreadName, *ThreadName);
}
}
}
FGraphTaskDelegate GraphTaskDelegate = FGraphTaskDelegate::CreateUObject(this, &ATaskGraphActor::OnTaskFinished);
if (DispatchWhenReady)
{ // 创建立即执行的任务,返回结构体参数
return FTaskGraphItem(ThreadName, TGraphTask<FTaskGraphWithPrerequisitesAndChild>::CreateTask(&PrerequisiteEvents).ConstructAndDispatchWhenReady(ThreadName, ChildGraphTask, GraphTaskDelegate));
}
// 创建任务后挂起,等待触发,返回结构体参数
return FTaskGraphItem(ThreadName, nullptr, TGraphTask<FTaskGraphWithPrerequisitesAndChild>::CreateTask(&PrerequisiteEvents).ConstructAndHold(ThreadName, ChildGraphTask, GraphTaskDelegate));
}
void ATaskGraphActor::TriggerGraphTask(FTaskGraphItem TaskGraphItem)
{
if (TaskGraphItem.m_GraphTask)
{
TaskGraphItem.m_GraphTask->Unlock();
UE_LOG(LogTemp, Warning, TEXT("Task %s Trigger!"), *TaskGraphItem.m_ThreadName);
}
}
扩展
ParallelFor
- 基于 TaskGraph
- 多次调用函数体,可以做简单的遍历处理
- bForceSingleThread 设置单线程还是多线程
ParallelFor
(
int32 Num,
TFunctionRef < void )> Body,
bool bForceSingleThread,
bool bPumpRenderingThread
)
void ParallelForWithPreWork
(
int32 Num,
TFunctionRef < void )> Body,
TFunctionRef < void ()> CurrentThreadWorkToDoBeforeHelping,
bool bForceSingleThread,
bool bPumpRenderingThread
)
ParallelFor(100, [](int32 CurrIdx) {
int32 Sum = 0;
for (int32 Idx = 0; Idx < CurrIdx * 100; ++Idx)
Sum += FMath::Sqrt(1234.56f);
});
AsyncTask
- 本质上使用 TaskGraph
//异步执行一个Function 函数指针
void AsyncTask(ENamedThreads::Type Thread, TUniqueFunction<void()> Function)
{
TGraphTask<FAsyncGraphTask>::CreateTask().ConstructAndDispatchWhenReady(Thread, MoveTemp(Function));
}
//异步执行一个 Lambda 表达式
void AsyncTask(ENamedThreads::Type Thread, [&](){} );
参考
- Legacy/Multi-Threading: Task Graph System
- 虚幻4与现代C++:基于任务的并行编程与TaskGraph入门
- 虚幻4与现代C++:使用TaskGraph实现Fork-Join模型
- UE4 C++进阶07 异步操作-基于TaskGraph的多线程
- 虚幻4 Task Graph System 介绍
- ParallelFor
- ParallelForWithPreWork
【UE4 C++ 基础知识】<13> 多线程——TaskGraph的更多相关文章
- 【UE4 C++ 基础知识】<11>资源的同步加载与异步加载
同步加载 同步加载会造成进程阻塞. FObjectFinder / FClassFinder 在构造函数加载 ConstructorHelpers::FObjectFinder Constructor ...
- 【UE4 C++ 基础知识】<12> 多线程——FRunnable
概述 UE4里,提供的多线程的方法: 继承 FRunnable 接口创建单个线程 创建 AsyncTask 调用线程池里面空闲的线程 通过 TaskGraph 系统来异步完成一些自定义任务 支持原生的 ...
- 【UE4 C++ 基础知识】<3> 基本数据类型、字符串处理及转换
基本数据类型 TCHAR TCHAR就是UE4通过对char和wchar_t的封装 char ANSI编码 wchar_t 宽字符的Unicode编码 使用 TEXT() 宏包裹作为字面值 TCHAR ...
- 【UE4 C++ 基础知识】<14> 多线程——AsyncTask
概念 AsyncTask AsyncTask 系统是一套基于线程池的异步任务处理系统.每创建一个AsyncTas,都会被加入到线程池中进行执行 AsyncTask 泛指 FAsyncTask 和 FA ...
- JAVA基础知识之多线程——三种实现多线程的方法及区别
所有JAVA线程都必须是Thread或其子类的实例. 继承Thread类创建线程 步骤如下, 定义Thead子类并实现run()方法,run()是线程执行体 创建此子类实例对象,即创建了线程对象 调用 ...
- java基础知识总结--多线程
1.扩展Java.lang.Thread类 1.1.进程和线程的区别: 进程:每个进程都有自己独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1~n个线程. 线程:同一类线 ...
- 【UE4 C++ 基础知识】<8> Delegate 委托
概念 定义 UE4中的delegate(委托)常用于解耦不同对象之间的关联:委托的触发者不与监听者有直接关联,两者通过委托对象间接地建立联系. 监听者通过将响应函数绑定到委托上,使得委托触发时立即收到 ...
- JAVA基础知识之多线程——线程组和未处理异常
线程组 Java中的ThreadGroup类表示线程组,在创建新线程时,可以通过构造函数Thread(group...)来指定线程组. 线程组具有以下特征 如果没有显式指定线程组,则新线程属于默认线程 ...
- JAVA基础知识之多线程——控制线程
join线程 在某个线程中调用其他线程的join()方法,就会使当前线程进入阻塞状态,直到被join线程执行完为止.join方法类似于wait, 通常会在主线程中调用别的线程的join方法,这样可以保 ...
随机推荐
- k8s 存活探针,滚动更新
文章原文 存活探针 Kubelet使用liveness probe(存活探针)来确定何时重启容器.例如,当应用程序处于运行状态但无法做进一步操作,liveness探针将捕获到deadlock,重启处于 ...
- MongoDB 常见问题 - 解决 brew services list 查看 MongoDB 服务 status 显示 error 的问题
问题背景 将 MongoDB 作为服务运行 brew services start mongodb-community@4.4 也显示运行成功了,但是查看服务列表的时候,发现 MongoDB 服务的还 ...
- Activiti 学习(二)—— Activiti 流程定义和部署
概述 在这一节,我们将创建一个 Activit 工作流,并启动这个流程,主要包含以下几个步骤: 定义流程,按照 BPMN 的规范,使用流程定义工具,用流程符号把整个流程描述出来 部署流程,把画好的流程 ...
- C语言中volatile、register、const、static、extern、 auto关键字的作用
一.volatile详解 volatile的本意是"易变的" 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据.当要求使用volat ...
- 经典多级时间轮定时器(C语言版)
经典多级时间轮定时器(C语言版) 文章目录 经典多级时间轮定时器(C语言版) 1. 序言 2. 多级时间轮实现框架 2.1 多级时间轮对象 2.2 时间轮对象 2.3 定时任务对象 2.4 双向链表 ...
- PPPoE技术白皮书(H3C)
PPPoE技术白皮书 关键词:PPP,Ethernet,PPPoE 摘要:PPPoE是一种通过一个远端接入设备为以太网上的主机提供接入服务,并可以对接入的每个主机实现控制和计费的技术.本文介绍了PPP ...
- redis存取数据String
一.连接不同数据库和存取String类型值 1.连接数据库 2.set和get多个 3.取值并赋值 取值返回的是赋值改变之前的值: 4.递增和递减 5.字符串尾部加值 6.商品编号自增应用
- 《微服务架构设计模式》读书笔记 | 第8章 外部API模式
目录 前言 1. 外部API的设计难题 1.1 FTGO应用程序的服务及客户端 1.2 FTGO移动客户端API的设计难题 1.3 其他类型客户端API的设计难题与特点 2. API Gateway模 ...
- python库--tensorflow--scope命名方式
方法 参数 说明 .name_scope() with...: name 在其下使用Variable, 变量名(V_n)前会被加上'name/...'且相同V_n会自动添加后缀加以区分, 使用ge ...
- 迷你商城后台管理系统---------stage3项目部署测试汇总
系统测试 在项目部署到云服务器之前,已通过本机启动springboot程序,访问localhost:8080,输入登陆的账户等一系列操作测试:功能测试.健壮性测试,系统已满足用户规定的需求. 系统部署 ...