目录
概念
使用示例1
使用示例2
概念
FGraphEventRef
本质上是对一个异步任务或者一组相关异步任务在虚幻引擎任务图系统中的一种引用(reference)。虚幻引擎的任务图系统用于高效地调度和管理各种异步任务,协调它们的执行顺序以及处理任务之间的依赖关系等,而 FGraphEventRef
就是开发者与这个任务图系统交互的一个关键 “接口”,通过它可以实现对任务的创建、跟踪以及依赖管理等操作。
使用示例1
先创建并执行一个简单的异步任务SimpleEvent
,等待其完成后,再批量创建 20 个异步任务存入 SimpleEventArray
中,最后等待这 20 个任务全部完成,以此体现任务图系统在协调多线程任务的执行顺序以及处理任务之间依赖关系的作用。
代码如下所示:
void UThreadSubsystem::GraphEvent()
{
FGraphEventRef SimpleEvent = FFunctionGraphTask::CreateAndDispatchWhenReady([this]() {
PrintLogInThread(TEXT("SimpleEvent Start"));
FPlatformProcess::Sleep(3);
PrintLogInThread(TEXT("SimpleEvent End"));
});
check(!SimpleEvent->IsComplete());
SimpleEvent->Wait();
FGraphEventArray SimpleEventArray;
for (size_t i = 0; i < 20; i++)
{
SimpleEventArray.Add(FFunctionGraphTask::CreateAndDispatchWhenReady([this,i]() {
FString Info1 = FString::Printf(TEXT("SimpleEvent Start -- %d"), i);
PrintLogInThread(Info1);
FPlatformProcess::Sleep(i%3);
FString Info2 = FString::Printf(TEXT("SimpleEvent End -- %d"), i);
PrintLogInThread(Info2);
}));
}
FTaskGraphInterface::Get().WaitUntilTasksComplete(SimpleEventArray);
}
void UThreadSubsystem::PrintLogInThread(FString Info)
{
AsyncTask(ENamedThreads::GameThread, [Info]() {
UE_LOG(LogTemp, Warning, TEXT("ThreadLog:[%s]"), *Info);
});
}
在第147行代码~151行代码中,通过 FFunctionGraphTask::CreateAndDispatchWhenReady
函数创建一个异步任务,并将返回的 FGraphEventRef
对象赋值给 SimpleEvent
。在该异步任务的 lambda 表达式中,通过调用 FPlatformProcess::Sleep(3);
让线程休眠 3 秒来模拟任务执行过程中的耗时操作。
第152行代码用于检查在创建任务后,任务是否立即就完成了(正常情况下刚创建的任务应该还未执行完,所以这里预期 IsComplete
返回 false
),如果出现不符合预期的情况(即任务已经完成),check
宏会触发断言失败,有助于在开发阶段发现可能的逻辑错误。
第153行代码调用 SimpleEvent->Wait();
会使当前线程阻塞,一直等待这个异步任务执行完毕,确保后续代码在该任务完成后才继续执行,实现了简单的任务同步机制,保证了任务执行顺序上的先后关系。
第155行代码创建了一个 FGraphEventArray
类型的数组 SimpleEventArray
,用于存放多个 FGraphEventRef
对象,每个对象对应一个异步任务。
第156~167行代码通过 for
循环创建20个异步任务
第169行代码通过调用 FTaskGraphInterface::Get().WaitUntilTasksComplete();
函数,让当前线程阻塞等待,直到 SimpleEventArray
数组中存放的所有 20 个异步任务都执行完毕,实现了对多个并行任务的同步等待功能。
执行结果如下:
使用示例2
该示例主要利用虚幻引擎的任务图系统来创建多个异步任务,并通过设置任务之间的依赖关系,实现特定的任务执行顺序。要实现的任务执行顺序如下图所示,我们需要先执行TaskA,TaskA执行完毕后开始同时执行TaskA1和TaskB,TaskB执行完毕后再执行TaskC,在TaskA1和TaskC都执行完毕的基础上再执行TaskD。
代码如下所示:
void UThreadSubsystem::BatchGraphEvent()
{
FGraphEventRef SimpleEventA = FFunctionGraphTask::CreateAndDispatchWhenReady([this]() {
PrintLogInThread(TEXT("SimpleEventA Start"));
FPlatformProcess::Sleep(3);
PrintLogInThread(TEXT("SimpleEventA End"));
});
FGraphEventRef SimpleEventB = FFunctionGraphTask::CreateAndDispatchWhenReady([this]() {
PrintLogInThread(TEXT("SimpleEventB Start"));
FPlatformProcess::Sleep(3);
PrintLogInThread(TEXT("SimpleEventB End"));
}, TStatId{}, SimpleEventA);
FGraphEventRef SimpleEventC = FFunctionGraphTask::CreateAndDispatchWhenReady([this]() {
PrintLogInThread(TEXT("SimpleEventC Start"));
FPlatformProcess::Sleep(3);
PrintLogInThread(TEXT("SimpleEventC End"));
}, TStatId{}, SimpleEventB);
FGraphEventRef SimpleEventA1 = FFunctionGraphTask::CreateAndDispatchWhenReady([this]() {
PrintLogInThread(TEXT("SimpleEventA1 Start"));
FPlatformProcess::Sleep(3);
PrintLogInThread(TEXT("SimpleEventA1 End"));
}, TStatId{}, SimpleEventA);
FGraphEventArray Prerequisite;
Prerequisite.Add(SimpleEventA1);
Prerequisite.Add(SimpleEventC);
FGraphEventRef SimpleEventD = FFunctionGraphTask::CreateAndDispatchWhenReady([this]() {
PrintLogInThread(TEXT("SimpleEventD Start"));
FPlatformProcess::Sleep(3);
PrintLogInThread(TEXT("SimpleEventD End"));
}, TStatId{}, &Prerequisite);
SimpleEventD->Wait();
PrintLogInThread(TEXT("All Tasks Completed"));
}
第174~178行代码创建了一个异步任务 SimpleEventA
第180~184行代码创建了一个异步任务 SimpleEventB
,并设置任务 SimpleEventB
设置了依赖于任务 SimpleEventA
第186~190行代码创建了一个异步任务 SimpleEventC
,并设置任务 SimpleEventC
设置了依赖于任务 SimpleEventB
第192~196行代码创建了一个异步任务 SimpleEventA1
,并设置任务 SimpleEventA1
设置了依赖于任务 SimpleEventA
第198~206行代码首先创建了一个 FGraphEventArray
类型的数组 Prerequisite
,并将SimpleEventA1
和 SimpleEventC
这两个 FGraphEventRef
对象添加到其中,以此表示一组前置任务条件。然后创建任务 SimpleEventD
时,将 Prerequisite
作为参数传入,意味着SimpleEventD
依赖于 SimpleEventA1
和 SimpleEventC
这两个任务,只有当这两个任务都执行完成后,SimpleEventD
才会开始执行。
第208行代码通过调用 SimpleEventD->Wait();
让当前线程阻塞等待,直到任务SimpleEventD
执行完毕。由于 SimpleEventD
依赖于前面多个任务,所以实际上是等待整个任务链上的所有相关任务都完成后,此阻塞才会解除。
执行结果如下:
如果要统计所有任务执行花费时间,可以通过添加如下代码实现
执行结果如下,总共花费12s,和预想的一样。