【UE5 C++课程系列笔记】24——多线程基础——Async

目录

概念

Async函数应用案例


概念

Async 函数的原型如下

cpp 复制代码
template<typename TFunction>
auto Async(EAsyncExecution::Type ExecutionType, TFunction&& Function) -> decltype(Function);

Async 函数是一个模板函数,接受两个主要参数:

(1)EAsyncExecution::Type ExecutionType:用于指定任务执行的方式或线程相关的属性。可指定如下参数:

cpp 复制代码
/** Execute in Task Graph (for short running tasks). */
TaskGraph,

/** Execute in Task Graph on the main thread (for short running tasks). */
TaskGraphMainThread,

/** Execute in separate thread if supported (for long running tasks). */
Thread,

/** Execute in separate thread if supported or supported post fork (see FForkProcessHelper::CreateThreadIfForkSafe) (for long running tasks). */
ThreadIfForkSafe,

/** Execute in global queued thread pool. */
ThreadPool,

(2)TFunction&& Function:这是一个可调用对象,通常可以是函数指针、lambda 表达式等,定义了具体要执行的任务逻辑。lambda表达式示例如下:

cpp 复制代码
Async(EAsyncExecution::Thread, []() {
    // 这里放置需要在单独线程中执行的任务逻辑,比如执行一些耗时计算
    int sum = 0;
    for (int i = 0; i < 1000; i++)
    {
        sum += i;
    }
});

Async函数应用案例

同时提交100个耗时任务。

如下代码所示,通过循环 100 次调用 AsyncTaskENamedThreads::AnyThread提交异步任务,每个任务只是执行 FPlatformProcess::Sleep(2),也就是让执行该任务的线程休眠 2 秒。

通过执行结果可以看到,执行任务的线程有很多重复的线程。由于提交了大量这样的任务,会使得线程池中大量的线程被占用并处于阻塞休眠状态。在这 2 秒内,这些线程无法去执行其他任务,而如果此时还有其他需要线程资源来执行的任务,就没办法获取到可用线程,进而导致整个程序的运行像是卡住了一样,无法顺利推进后续业务逻辑。

改进:

通过循环 100 次调用 Async 函数提交异步任务,每个任务执行 FPlatformProcess::Sleep(10) 让线程休眠 10 秒,但虚幻引擎内部有一套相对复杂且智能的线程调度机制。在这种机制下,它会尝试合理地分配线程资源来执行这些任务,不会像 BlockThreadPool 函数那样简单粗暴地让大量线程同时进入阻塞休眠状态。虚幻引擎可能会根据系统当前的负载情况、线程优先级等因素,对任务进行排队或者分批次地安排线程去执行,避免一次性将所有线程资源耗尽,使得其他必要的任务依然有机会获取到线程来执行,从而保证程序整体上还能继续运行,不会出现明显的卡顿或卡住情况。

执行结果如下,可以看到执行任务的线程是100个不同的线程。

为了获取每个任务的结果,继续做如下改进:

cpp 复制代码
void UThreadSubsystem::GetAsyncFuture()
{
    AsyncTask(ENamedThreads::AnyThread, [this]() {
        TArray<TFuture<int32>> FutureResults;

        for (size_t i = 0; i < 100; i++)
        {
            FutureResults.AddDefaulted();
            FutureResults.Last()=
            Async(EAsyncExecution::Thread, [this, i]()->int32 {
                int32 SleepTime = i % 5;

                FPlatformProcess::Sleep(SleepTime);

                uint32 CurrentID = FPlatformTLS::GetCurrentThreadId();
                FString CurrentThread = FThreadManager::Get().GetThreadName(CurrentID);
                FString Info = FString::Printf(TEXT("-- Thread Finished -- CurrentThreadID:[%d] -- CurrentThreadName:[%s] -- SleepTime:%d"), CurrentID, *CurrentThread, SleepTime);
                PrintLogInThread(Info);
                return SleepTime;
            });
        }

        PrintLogInThread(TEXT("Task construction completed"));

        for (auto& TmpFuture : FutureResults)
        {
            int32 SleepTime = TmpFuture.Get();
            PrintLogInThread(FString::FromInt(SleepTime));
        }

        PrintLogInThread(TEXT("Task execution completed"));
    });
}

首先通过 AsyncTask 函数将一个 lambda 表达式提交到 ENamedThreads::AnyThread 所代表的任意可用线程中执行。在 lambda 表达式中捕获了当前类指针 this,以便在这个异步任务内部能够访问类的成员函数和成员变量。

然后在 AsyncTask 函数内部创建了一个 TArray<TFuture<int32>> 类型的数组 FutureResults,用于存放 100 个 TFuture 对象,每个对象将与一个内层异步任务相关联,后续通过这些对象来获取对应异步任务的执行结果。

在for循环中,使用 FutureResults.AddDefaulted()FutureResults 数组添加一个默认初始化的 TFuture<int32> 对象,然后通过 FutureResults.Last() 获取数组中刚添加的这个默认元素,并将 Async 函数返回的 TFuture 对象赋值给它。这里的 Async 函数用于创建实际执行具体任务逻辑的内层异步任务,传入参数 EAsyncExecution::Thread 表示每个内层异步任务会在单独的线程中执行。传递的 lambda 表达式定义了具体的任务逻辑,它捕获了当前类指针 this 和循环变量 i,并指定返回值类型为 int32

最后通过一个 for 循环遍历 FutureResults 数组中的每个 TFuture 对象,然后调用 TmpFuture.Get() 来获取对应的内层异步任务结果。

执行效果如下:

应用案例代码:

"ThreadSubsystem.h"

cpp 复制代码
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "SimpleRunnable.h"
#include "HAL/ThreadManager.h"
#include "ThreadSubsystem.generated.h"

UCLASS()
class STUDY_API UThreadSubsystem : public UGameInstanceSubsystem
{
	GENERATED_BODY()

public:
	virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;

public:
	UFUNCTION(BlueprintCallable)
	void BlockThreadPool();  //阻塞主线程的测试函数

	UFUNCTION(BlueprintCallable)
	void InitAsync();

	UFUNCTION(BlueprintCallable)
	void GetAsyncFuture();

	void PrintLogInThread(FString Info);
};

"ThreadSubsystem.cpp"

cpp 复制代码
// Fill out your copyright notice in the Description page of Project Settings.


#include "Thread/ThreadSubsystem.h"

bool UThreadSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
    return true;
}

void UThreadSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
    Super::Initialize(Collection);
    //InitSimpleThread();
}

void UThreadSubsystem::Deinitialize()
{
    //ReleaseSimpleThread();
    Super::Deinitialize();
}

void UThreadSubsystem::BlockThreadPool()
{
    for (size_t i = 0; i < 100; i++)
    {
        AsyncTask(ENamedThreads::AnyThread, []() {
            FPlatformProcess::Sleep(2);

            uint32 CurrentID = FPlatformTLS::GetCurrentThreadId();
            FString CurrentThread = FThreadManager::Get().GetThreadName(CurrentID);
            FString Info = FString::Printf(TEXT("-- Thread Finished -- CurrentThreadID:[%d] -- CurrentThreadName:[%s]"), CurrentID, *CurrentThread);
            AsyncTask(ENamedThreads::GameThread, [Info]() {
                UE_LOG(LogTemp, Warning, TEXT("ThreadLog:[%s]"), *Info);
            });
        });
    }
}

void UThreadSubsystem::InitAsync()
{
    for (size_t i = 0; i < 100; i++)
    {
        Async(EAsyncExecution::Thread, [this]() {
            FPlatformProcess::Sleep(10);

            uint32 CurrentID = FPlatformTLS::GetCurrentThreadId();
            FString CurrentThread = FThreadManager::Get().GetThreadName(CurrentID);
            FString Info = FString::Printf(TEXT("-- Thread Finished -- CurrentThreadID:[%d] -- CurrentThreadName:[%s]"), CurrentID, *CurrentThread);
            PrintLogInThread(Info);
        });
    }
}

void UThreadSubsystem::GetAsyncFuture()
{
    AsyncTask(ENamedThreads::AnyThread, [this]() {
        TArray<TFuture<int32>> FutureResults;

        for (size_t i = 0; i < 100; i++)
        {
            FutureResults.AddDefaulted();
            FutureResults.Last()=
            Async(EAsyncExecution::Thread, [this, i]()->int32 {
                int32 SleepTime = i % 5;

                FPlatformProcess::Sleep(SleepTime);

                uint32 CurrentID = FPlatformTLS::GetCurrentThreadId();
                FString CurrentThread = FThreadManager::Get().GetThreadName(CurrentID);
                FString Info = FString::Printf(TEXT("-- Thread Finished -- CurrentThreadID:[%d] -- CurrentThreadName:[%s] -- SleepTime:%d"), CurrentID, *CurrentThread, SleepTime);
                PrintLogInThread(Info);
                return SleepTime;
            });
        }

        PrintLogInThread(TEXT("Task construction completed"));

        for (auto& TmpFuture : FutureResults)
        {
            int32 SleepTime = TmpFuture.Get();
            PrintLogInThread(FString::FromInt(SleepTime));
        }

        PrintLogInThread(TEXT("Task execution completed"));
    });
}

void UThreadSubsystem::PrintLogInThread(FString Info)
{
    AsyncTask(ENamedThreads::GameThread, [Info]() {
        UE_LOG(LogTemp, Warning, TEXT("ThreadLog:[%s]"), *Info);
    });
}
相关推荐
1204157137 肖哥10 分钟前
UE5.7 灯光详解
ue5
狂云歌1 天前
AI写UE的3Dgame
人工智能·3d·ue5·游戏程序
日月云棠5 天前
UE5源码分析之Editor——Blutility模块全面分析
ue5
日月云棠5 天前
UE5源码分析之Editor——BehaviorTreeEditor模块全面分析
ue5
日月云棠5 天前
UE5源码分析之Editor——AssetTagsEditor模块全面分析
ue5
远离UE46 天前
UE5 显存 虚拟内存 深入学习笔记
笔记·学习·ue5
日月云棠6 天前
UE5源码分析之Editor——AnimationSettings模块全面分析
ue5
远离UE47 天前
UE5 SF_VertexShader 如何使用
ue5
电子云与长程纠缠7 天前
UE5 Lyra PocketWorld进行3D内容UI预览 - 上
开发语言·学习·3d·ue5·游戏引擎
电子云与长程纠缠7 天前
UE5 Lyra PocketWorld进行3D内容UI预览 - 下
开发语言·学习·游戏·ui·ue5