概述

  • 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. JDK1.8源码阅读笔记(2) AtomicInteger AtomicLong AtomicBoolean原子类

    JDK1.8源码阅读笔记(2) AtomicInteger AtomicLong AtomicBoolean原子类 Unsafe Java中无法直接操作一块内存区域,不能像C++中那样可以自己申请内存 ...

  2. Shiro03

    1.shiro授权角色.权限 2.Shiro的注解式开发 shiro权限思路 授权 ShiroUserMapper中定义两个方法 // 通过用户ID查询角色 Set<String> get ...

  3. Linux 三剑客(1)- grep

    作用 在文件或标准输入中,通过正则表达式查找对应的内容 语法格式 grep [选项]... PATTERN [FILE]... grep的常用选项参数 参数选项 描述 -G 默认值 -F 相当于使用f ...

  4. 随机生成uuid序号

    function guid() { function S4() { return (((1+Math.random())*0x10000)|0).toString(16).substring(1); ...

  5. 常见shell脚本测试题 for/while语句

    1.计算从1到100所有整数的和2.提示用户输入一个小于100的整数,并计算从1到该数之间所有整数的和3.求从1到100所有整数的偶数和.奇数和4.执行脚本输入用户名,若该用户存在,输出提示该用户已存 ...

  6. Java 语法学习2

    Java基础语法二 类型转换 public class demo03 { public static void main(String[] args) { int i=128; byte a=(byt ...

  7. C# 多线程编程之锁的使用【互斥锁(lock)和读写锁(ReadWriteLock)】

    多线程编程之锁的使用[互斥锁(lock)和读写锁(ReadWriteLock)] http://blog.csdn.net/sqqyq/article/details/18651335 多线程程序写日 ...

  8. k8s标签label

    1.给节点设置标签 一遍pod部署选择 kubectl label node 节点名 disktype=ssd kubectl label node master1 disktype=ssd 效果 [ ...

  9. 【OI】WERTYU UVa 10082

    题目: A common typing error is to place the hands on the keyboard one row to the right of the correct ...

  10. Deprecated: __autoload() is deprecated, use spl_autoload_register()

    Deprecated: __autoload() is deprecated, use spl_autoload_register() 解决:可能原因PHP版本过高,亲测discuz3.4版本使用ph ...