概述

  • 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, [&](){} );

参考

【UE4 C++ 基础知识】<13> 多线程——TaskGraph的更多相关文章

  1. 【UE4 C++ 基础知识】<11>资源的同步加载与异步加载

    同步加载 同步加载会造成进程阻塞. FObjectFinder / FClassFinder 在构造函数加载 ConstructorHelpers::FObjectFinder Constructor ...

  2. 【UE4 C++ 基础知识】<12> 多线程——FRunnable

    概述 UE4里,提供的多线程的方法: 继承 FRunnable 接口创建单个线程 创建 AsyncTask 调用线程池里面空闲的线程 通过 TaskGraph 系统来异步完成一些自定义任务 支持原生的 ...

  3. 【UE4 C++ 基础知识】<3> 基本数据类型、字符串处理及转换

    基本数据类型 TCHAR TCHAR就是UE4通过对char和wchar_t的封装 char ANSI编码 wchar_t 宽字符的Unicode编码 使用 TEXT() 宏包裹作为字面值 TCHAR ...

  4. 【UE4 C++ 基础知识】<14> 多线程——AsyncTask

    概念 AsyncTask AsyncTask 系统是一套基于线程池的异步任务处理系统.每创建一个AsyncTas,都会被加入到线程池中进行执行 AsyncTask 泛指 FAsyncTask 和 FA ...

  5. JAVA基础知识之多线程——三种实现多线程的方法及区别

    所有JAVA线程都必须是Thread或其子类的实例. 继承Thread类创建线程 步骤如下, 定义Thead子类并实现run()方法,run()是线程执行体 创建此子类实例对象,即创建了线程对象 调用 ...

  6. java基础知识总结--多线程

    1.扩展Java.lang.Thread类 1.1.进程和线程的区别: 进程:每个进程都有自己独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1~n个线程. 线程:同一类线 ...

  7. 【UE4 C++ 基础知识】<8> Delegate 委托

    概念 定义 UE4中的delegate(委托)常用于解耦不同对象之间的关联:委托的触发者不与监听者有直接关联,两者通过委托对象间接地建立联系. 监听者通过将响应函数绑定到委托上,使得委托触发时立即收到 ...

  8. JAVA基础知识之多线程——线程组和未处理异常

    线程组 Java中的ThreadGroup类表示线程组,在创建新线程时,可以通过构造函数Thread(group...)来指定线程组. 线程组具有以下特征 如果没有显式指定线程组,则新线程属于默认线程 ...

  9. JAVA基础知识之多线程——控制线程

    join线程 在某个线程中调用其他线程的join()方法,就会使当前线程进入阻塞状态,直到被join线程执行完为止.join方法类似于wait, 通常会在主线程中调用别的线程的join方法,这样可以保 ...

随机推荐

  1. 大厂的 SDK 写法,偷学到了!

    自己动手写 SDK 的经验技巧分享 大家好,我是鱼皮. 最近因为工作需要,自己动手写了一些项目的通用 SDK.在编写的过程中,我阅读和参考了不少公司中其他大佬写的 SDK,也总结了一些开发 SDK 的 ...

  2. SpringBoot快速集成SpringBootAdmin管控台监控服务

    SpringBootAdmin是一个针对 Spring Boot 的 Actuator 接口进行 UI 美化封装的监控工具,它可以在列表中浏览所有被监控 spring-boot 项目的基本信息.详细的 ...

  3. Appium问题解决方案(10)- Original error: Swipe did not complete successfully

    背景 从搜索页面返回首页之后,执行  swipe 滑动操作,但是报错了,如上图 解决方法 只需要在第一次 swipe 之前加个 sleep,强制等待即可 备注 这种解决方案其实不好,强制等待能少用就少 ...

  4. Intel® QAT加速卡之逻辑实例

    Intel QAT加速卡逻辑实例 1. QAT相关的名词组织关系 在本手册中描述的平台上,处理器可以连接到一个或多个英特尔通信芯片组8925至8955系列(PCH)设备. 从软件角度来看,每个PCH设 ...

  5. netfilter框架之hook点

    1. Netfilter中hook的所在位置 当网络上有数据包到来时,由驱动程序将数据包从网卡内存区通过DMA转移到设备主存区(内存区), 之后触发中断通知CPU进行异步响应,之后ip_rcv函数会被 ...

  6. Webpack:打包项目报错(eslint: debugger)

    打包项目需要把项目中的debugger删除,否则会报错.

  7. Expression 表达式动态生成

    http://blog.csdn.net/duan1311/article/details/51769119 以上是拼装和调用GroupBy的方法,是不是很简单,只要传入分组列与合计列就OK了! 下面 ...

  8. VMware ESXi 7.0 U2 SLIC 2.6 & Unlocker 集成 Intel NUC 网卡、USB 网卡和 NVMe 驱动

    ESXi 7 U2 标准版镜像集成 NUC 网卡.USB 网卡 和 NVMe 驱动. 请访问原文链接:https://sysin.org/blog/vmware-esxi-7-u2-nuc-usb-n ...

  9. Django学习day08随堂笔记

    今日考题 """ 今日考题 1.聚合查询,分组查询的关键字各是什么,各有什么特点或者注意事项 2.F与Q查询的功能,他们的导入语句是什么,针对Q有没有其他用法 3.列举常 ...

  10. TP5增加扩展配置目录

    ThinkPHP5.0.1版本开始增加了扩展配置目录的概念,在应用配置目录或者模块配置目录下面增加extra子目录,下面的配置文件都会自动加载,无需任何配置. 这极大的方便了我们进行扩展配置,比如在a ...