Skip to content

DigitalLife

根据你提供的头文件代码,这个 FSendMessageAsyncTask 线程的核心执行逻辑全部集中在 DoWork() 函数中(虽然具体代码没贴出来,但根据 UE 多线程规范和 Socket 编程惯例,其逻辑非常固定)。

这个线程的作用是:在后台死循环中,不断检查是否有数据需要发送,如果有,就通过 Socket 发送给服务器。

以下是详细的逻辑拆解:

1. 核心入口:DoWork()

当你调用 StartBackgroundTask() 时,UE 引擎会在线程池中分配一个线程,并自动执行 DoWork() 函数。

由于类继承自 FNonAbandonableTask,这个任务不能被中途强制取消,它必须自己运行到结束(通常是程序退出或 Socket 断开)。

2. 推测的内部逻辑流程

DoWork() 内部,代码逻辑通常如下:

  1. 开启死循环

    while (!bStop) { ... }
    
    线程会一直活着,直到 bStop 变为 true(通常由析构函数或外部逻辑触发)。

  2. 数据检查(生产者-消费者模型): 线程会检查 Buff 数组是否有数据。

    • Buff:这是发送缓冲区。主线程(游戏逻辑)会把要发送的数据(比如按键信息)写入这个数组。
    • BuffSize:记录当前缓冲区里有多少有效数据。
  3. 发送数据: 如果 BuffSize > 0,线程会调用 Socket 的发送函数:

    int32 BytesSent = 0;
    // 将 Buff 中的数据通过 Socket 发送出去
    Socket->Send(Buff.GetData(), BuffSize, BytesSent);
    

  4. 清理与休眠

    • 发送完成后,清空 Buff 或重置 BuffSize,准备接收下一批数据。
    • 为了防止后台线程占用 100% 的 CPU(空转),通常会加一个微小的延时:
      FPlatformProcess::Sleep(0.001f); // 休眠 1 毫秒
      

3. 关键成员变量的作用

变量名 作用 线程安全提示
Buff 发送队列。主线程往里写,后台线程往外读。 高风险:通常需要加锁(FCriticalSection),防止主线程正在写数据时,后台线程同时去读,导致数据错乱。
BuffSize 数据长度。告诉后台线程这次要发多少字节。 同上,需要同步保护。
Socket 通信句柄。用于实际的网络传输。 只读,线程安全。
bStop 停止标志。用于优雅地关闭线程。 建议使用原子操作或加锁访问。

4. 析构函数 ~FSendMessageAsyncTask()

虽然代码没显示,但析构函数通常负责: 1. 将 bStop 设为 true,通知 DoWork 里的循环退出。 2. 等待 DoWork 彻底结束。 3. 关闭 Socket(如果 Socket 生命周期由它管理)。

总结

这个线程就是一个“搬运工”。 * 主线程负责产生数据(比如你按下了 'A' 键),把 'A' 扔进 Buff。 * 这个后台线程负责盯着 Buff,一旦发现里面有东西,就立刻通过网线(Socket)发给服务器,从而保证主线程永远不会因为网络卡顿而卡死。

要让 `DoWork()` 函数被系统自动识别并在线程池中执行,你的类**必须**满足以下两个核心条件:

### 1. 继承 `FNonAbandonableTask`
这是最关键的一步。
*   **位置**:定义在 `<Async/AsyncWork.h>` 中。
*   **作用**:它告诉 UE 的线程池系统:“我是一个标准的后台任务,请在线程空闲时执行我的 `DoWork()` 函数。”
*   **特性**:`FNonAbandonableTask` 意味着这个任务一旦开始,就会一直运行直到完成(或者程序退出),不能被系统强制中断。

### 2. 实现 `GetStatId()`
这是 UE 线程系统的强制要求,用于性能统计。
*   **代码**:就是你代码中那段 `FORCEINLINE TStatId GetStatId() const ...`。
*   **作用**:如果不写这个,编译器会报错,因为它无法追踪这个任务的性能开销。

---

### 标准代码模板
如果你想自己写一个后台任务,直接套用下面这个结构即可:

```cpp
#include "CoreMinimal.h"
#include "Async/AsyncWork.h" // 必须包含这个头文件

// 1. 必须继承 FNonAbandonableTask
class FMyCustomTask : public FNonAbandonableTask
{
public:
    // 构造函数:可以在这里传入需要的数据(比如字符串、整数等)
    FMyCustomTask(FString InMessage) 
        : Message(InMessage) 
    {
    }

    // 2. 必须实现 DoWork
    void DoWork()
    {
        // 这里写你的耗时逻辑
        // 比如:循环发送数据、加载大文件、复杂计算
        UE_LOG(LogTemp, Log, TEXT("任务开始: %s"), *Message);

        // 模拟耗时操作
        FPlatformProcess::Sleep(2.0f); 

        UE_LOG(LogTemp, Log, TEXT("任务结束"));
    }

    // 3. 必须实现 GetStatId
    FORCEINLINE TStatId GetStatId() const
    {
        RETURN_QUICK_DECLARE_CYCLE_STAT(FMyCustomTask, STATGROUP_ThreadPoolAsyncTasks);
    }

private:
    FString Message; // 私有成员变量
};

启动方式

定义好类后,使用 FAutoDeleteAsyncTask 启动它:

// 创建并启动任务
(new FAutoDeleteAsyncTask<FMyCustomTask>(TEXT("Hello UE5")))->StartBackgroundTask();

总结

  • 必须继承FNonAbandonableTask
  • 必须包含#include "Async/AsyncWork.h"
  • 必须实现void DoWork()GetStatId() ```
☁️ 部署建议
如果你打算长期运行项目(博客 / API / 自动化脚本),建议直接用云服务器,会比本地稳定很多。
👉 查看云服务器(新用户优惠)