【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 構造函數參數
    }
    

    image

了解任務的創建

在上一小節中,我們可以使用以下代碼創建任務

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 的完成

image

image

派發任務

  • 如圖所示,虛線框為派發子任務
  • B 的開始依賴於 A0 和 A1 的完成
  • B 有兩個子任務 B0 和 B1,B 的完成 需要滿足 B0 和 B1 也完成
  • C0 和 C1 的開始依賴於B 的完成,因而 C0 和 C1 的開始 需要滿足 B、B0 和 B1 都完成

image

image

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

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM