UE4 TaskGraph 学习笔记:介绍与实践
这个东西跟 Unity 中的 Job 很相似,都是基于任务的并行程序设计,可以处理资源竞争与执行顺序问题
简单介绍
简单说就是一张有向无环图,每一个任务是节点,任务的依赖关系是边,根据拓扑排序规划任务的执行。如果一个节点的入度为零就可以并行,并且执行完成后删掉自己的出边;否则等待入边的节点执行完成
- 节点是
TGraphTask
模版类,我们需要自己实现任务类满足它的要求。重点是实现DoTask
方法 - 边是
FGraphEventRef
,可以在CreateTask
之后得到,可以理解为任务完成的事件;同时在CreateTask
的时候可以把其他任务的FGraphEventRef
以FGraphEventArray
的形式传入以表示依赖(入边)
另外,任务类的 DoTask
方法参数中有一个 const FGraphEventRef &MyCompletionGraphEvent
,根据变量名我们知道它是任务自身的完成事件。
于是我们还可以在这个东西上进行蛇皮操作,比如在本任务完成之前再等待另一个任务完成,但是写起来很乱
Hello World
跟随下面的指引建立两个任务类,然后通过 FGraphEventRef
指定他们的执行顺序
准备两个任务类 TestTaskA
和 TestTaskB
TestTaskA
的代码如下,另外一个可以仿照着写
class TestTaskA
{
public:
TestTaskA() {
// 底层使用了可变参数模板,因此构造函数可以添加任意的参数,但是注意不能传入引用
}
// 返回任务名
static const TCHAR* GetTaskName() { return TEXT("TestTask"); }
FORCEINLINE static TStatId GetStatId() { RETURN_QUICK_DECLARE_CYCLE_STAT(TestTask, STATGROUP_TaskGraphTasks); }
// 返回 Task 所在的线程
static ENamedThreads::Type GetDesiredThread() { return ENamedThreads::AnyThread; }
static ESubsequentsMode::Type GetSubsequentsMode()
{
return ESubsequentsMode::TrackSubsequents;
}
// 任务的执行函数
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
// 这里是任务的逻辑代码
for (int Idx = 0; Idx < 5; Idx++)
UE_LOG(LogTemp, Log, TEXT("Task A executing"));
UE_LOG(LogTemp, Log, TEXT("Task A completed"));
}
};
创建一个 Actor,并修改 BeginPlay
方法如下
void AMyActor::BeginPlay()
{
Super::BeginPlay();
FGraphEventRef TaskA = TGraphTask<TestTaskA>::CreateTask().ConstructAndDispatchWhenReady();
FGraphEventArray DependencyForTaskB = { TaskA };
FGraphEventRef TaskB = TGraphTask<TestTaskB>::CreateTask(&DependencyForTaskB).ConstructAndDispatchWhenReady();
}
ParallelFor
UE4 有一个 ParallelFor
,是对于简单遍历的并行处理,基于 TaskGraph,具体去看 Runtime/Core/Public/Async/ParallelFor.h
下面给个简单的例子
ParallelFor(100, [](int32 CurrIdx) {
int32 Sum = 0;
for (int32 Idx = 0; Idx < CurrIdx * 100; ++Idx)
Sum += FMath::Sqrt(1234.56f);
});
对比 Unity C# Job System
如果抽象成一张 DAG 来看,这两个本质上其实就是同一个东西。简单说就是通过建立依赖关系避免资源竞争实现易于管理的并行
但是 Job System 相对于 TaskGraph 在用户代码中出现频率要高得多。因为 Unity DOTS 天生就能很好的设计并行,甚至 Job 其实是嵌入在 SystemBase
里的,对于游戏主逻辑都能被频繁使用;而 UE4 是 OOP,个人理解的话应该主要用于耗时长的计算或者 I/O 任务等,但是把游戏主逻辑放到 TaskGraph 里是比较困难的
另外有一个小区别,UE4 的 ParallerFor
与 Unity 里的 ScheduleParallel
是不太一样的。UE4 的 ParallelFor 其实是基于 TaskGraph 的顶层;而 Unity 的 ScheduleParallel
是 Job 本身自带的方法