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

news2025/1/10 18:22:39

目录

概念

Async函数应用案例


概念

   Async 函数的原型如下 

template<typename TFunction>
auto Async(EAsyncExecution::Type ExecutionType, TFunction&& Function) -> decltype(Function);

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

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

/** 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表达式示例如下:

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

Async函数应用案例

同时提交100个耗时任务。

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

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

改进: 

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

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

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

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”

// 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” 

// 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);
    });
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2274486.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【环境搭建】Metersphere v2.x 容器部署教程踩坑总结

前言 Metersphere部署过程中遇到的问题有点多&#xff0c;原因是其容器的架构蛮复杂的&#xff0c;比较容易踩坑&#xff0c;所以记录一下。 介绍 MeterSphere 是开源持续测试平台&#xff0c;遵循 GPL v3 开源许可协议&#xff0c;涵盖测试管理、接口测试、UI 测试和性能测…

RabbitMQ-SpringAMQP使用介绍

RabbitMQ 1. Spring AMQP1.1 引入依赖1.2 消息发送1.3 消息接收1.4 WorkQueue模型1.4.1 实例代码1.4.2 能者多劳1.4.3 总结 1.5交换机1.6 Fanout交换机&#xff08;广播&#xff09;1.7 Direct交换机&#xff08;订阅&#xff09;1.8 Topic交换机&#xff08;通配符订阅&#x…

Can I Use 实战指南:优化你的前端开发流程

引言 浏览器兼容性问题一直是前端开发中最令人头疼的难题。无论是新技术的支持差异&#xff0c;还是老旧浏览器的兼容需求&#xff0c;开发者常常需要花费大量时间解决这些问题。而 Can I Use 正是为此而生的前端开发利器。它提供详尽的浏览器兼容性数据&#xff0c;帮助开发者…

会员制营销与门店业绩提升:以开源AI智能名片S2B2C商城小程序为例的深度剖析

摘要&#xff1a;在数字化时代&#xff0c;会员制营销已成为企业提升门店业绩、增强客户黏性的重要策略。然而&#xff0c;仅仅将会员制营销视为提升业绩的手段&#xff0c;显然过于笼统&#xff0c;缺乏精准性。本文基于“业绩客量客单回头次数”的公式&#xff0c;深入探讨了…

/src/utils/request.ts:axios 请求封装,适用于需要统一处理请求和响应的场景

文章目录 数据结构解释1. 核心功能2. 代码结构分析请求拦截器响应拦截器 3. 改进建议4. 总结 console.log(Intercepted Response:, JSON.stringify(response));{"data": {"code": 0,"msg": "成功","data": {"id":…

Ubuntu24.04安装AppImage报错AppImages require FUSE to run.

报错如下&#xff1a; 解决&#xff1a; sudo apt install libfuse2t64如果不行&#xff1a; sudo add-apt-repository universe sudo apt install libfuse2t64安装时又报错&#xff1a; [10354:0109/100149.571068:FATAL:setuid_sandbox_host.cc(158)] The SUID sandbox hel…

2025新春烟花代码(二)HTML5实现孔明灯和烟花效果

效果展示 源代码 <!DOCTYPE html> <html lang"en"> <script>var _hmt _hmt || [];(function () {var hm document.createElement("script");hm.src "https://hm.baidu.com/hm.js?45f95f1bfde85c7777c3d1157e8c2d34";var …

同步整流 MT6705 应用注意事项

MT6705 是用于反激式变换器的高性能45V 同步整流器。它兼容各种反激转换器类型。支持 DCM、CCM 和准谐振模式。MT6705集成了一个40V功率MOSFET&#xff0c;可以取代肖特基二极管&#xff0c;提高效率。MT6705 PCB 布局应遵循以下规则: 1、VCC 电容器: VCC 引脚必须放置电容,电容…

LLaMA-Factory web微调大模型并导出大模型

LLaMA-Factory 开源大模型如LLaMA&#xff0c;Qwen&#xff0c;Baichuan等主要都是使用通用数据进行训练而来&#xff0c;其对于不同下游的使用场景和垂直领域的效果有待进一步提升&#xff0c;衍生出了微调训练相关的需求&#xff0c;包含预训练&#xff08;pt&#xff09;&am…

Jenkins内修改allure报告名称

背景&#xff1a; 最近使用Jenkins搭建自动化测试环境时&#xff0c;使用Jenkins的allure插件生成的报告&#xff0c;一直显示默认ALLURE REPORT&#xff0c;想自定义成与项目关联的名称&#xff0c;如图所示&#xff0c;很明显自定义名称显得高大上些&#xff0c;之前…

RK3588平台开发系列讲解(系统篇)Linux Kconfig的语法

文章目录 一、什么是Kconfig二、config模块三、menuconfig四、menu 和 endmenu五、choice 和 endchoice六、source七、depends on八、default九、help十、逻辑表达式沉淀、分享、成长,让自己和他人都能有所收获!😄 一、什么是Kconfig Kconfig的语法及代码结构非常简单。本博…

Android原生开发同一局域网内利用socket通信进行数据传输

1、数据接收端代码如下&#xff0c;注意&#xff1a;socket 接收信息需要异步运行&#xff1a; // port 端口号自定义一个值&#xff0c;比如 8888&#xff0c;但需和发送端使用的端口号保持一致 ServerSocket serverSocket new ServerSocket(port); while (true) {//这里为了…

Elasticsearch学习(1) : 简介、索引库操作、文档操作、RestAPI、RestClient操作

目录 1.elasticsearch简介1.1.了解es1.2.倒排索引正向索引和倒排索引 1.3.es的一些概念:文档和字段&#xff1b;索引和映射&#xff1b;Mysql与ES1.4.安装es、kibana部署单点es部署kibanaIK分词器安装IK分词器与测试扩展与停用词词典总结 部署es集群 2.索引库操作2.1.mapping映…

Clickhouse基础(一)

操作命令&#xff1a; sudo clickhouse start sudo clickhouse restart sudo clickhouse status进入clickhouse clickhouse-client -mCREATE TABLE db_13.t_assist (modelId UInt64,taskId UInt64,testNo String,tdId UInt64,eventDay String,eventDaytime UInt64,eventBatch …

记录将springboot的jar包和lib分离,使用docker-compose部署

本文讲诉如何把jar里的lib依赖包独立出来&#xff0c;方便更新服务时&#xff0c;缩小jar的体积&#xff0c;下面以若依的system服务为例&#xff0c;配置中的路径请酌情修改&#xff0c;主要提供大致配置逻辑 第一步&#xff1a;修改项目的pom.xml&#xff0c;调整build的配…

【对象存储】-- s3:\\、s3n:\\、s3a:\\ 简介

目录 1. s3:\ 2. s3n:\ 3. s3a:\ 区别对比 总结 在 Hadoop 和大数据处理领域&#xff0c;s3:\\、s3n:\\ 和 s3a:\\ 是访问 Amazon S3 的不同文件系统实现方式。以下是它们的简要介绍、区别及应用场景&#xff1a; 1. s3:\ 全称&#xff1a;Hadoop S3 Native FileSystem。…

Springboot3.x工程创建及必要引用(基础篇)

下面从环境的安装和配置开始&#xff0c;到Springboot3.x工程创建&#xff0c;记录一下让完全没有基础的小白用户也能够开始自己的第一个项目。 准备 安装JDK环境&#xff08;这里最好安装JDK17及以上版本&#xff09;安装IntelliJ IDEA Ultimate工具&#xff08;可以从官网下…

腾讯云AI代码助手-公司职位分析AI助手

作品简介 腾讯云AI代码助手是一款智能工具&#xff0c;专注于为公司提供职位分析服务。通过自然语言处理和机器学习技术&#xff0c;它能快速解析职位描述&#xff0c;提取关键信息&#xff0c;并提供数据驱动的洞察&#xff0c;帮助公司优化招聘流程和职位设计。 技术架构 …

QML学习(八) Quick中的基础组件:Item,Rectangle,MouseArea说明及使用场景和使用方法

上一篇中我们从设计器里可以看到Qt Quick-Base中有几大基础组件&#xff0c;如下图&#xff0c;这篇文章先介绍下Item&#xff0c;Rectangle&#xff0c;MouseArea这三个的说明及使用场景和使用方法 Item Item 是 QML 中所有可视元素的基类&#xff0c;是一个非常基础和通用的…

宇航用VIRTEX5系列FPGA的动态刷新方法及实现

SRAM型FPGA在宇航领域有广泛的应用&#xff0c;为解决FPGA在空间环境中的单粒子翻转问题&#xff0c;增强设计的可靠性&#xff0c;本文介绍一种低成本的抗辐照解决方案。该方案从外置高可靠存储器中读取配置数据&#xff0c;通过定时刷新结合三模冗余的方式消除单粒子影响&…