DigitalLife
根据你提供的头文件代码,这个 FSendMessageAsyncTask 线程的核心执行逻辑全部集中在 DoWork() 函数中(虽然具体代码没贴出来,但根据 UE 多线程规范和 Socket 编程惯例,其逻辑非常固定)。
这个线程的作用是:在后台死循环中,不断检查是否有数据需要发送,如果有,就通过 Socket 发送给服务器。
以下是详细的逻辑拆解:
1. 核心入口:DoWork()
当你调用 StartBackgroundTask() 时,UE 引擎会在线程池中分配一个线程,并自动执行 DoWork() 函数。
由于类继承自 FNonAbandonableTask,这个任务不能被中途强制取消,它必须自己运行到结束(通常是程序退出或 Socket 断开)。
2. 推测的内部逻辑流程
在 DoWork() 内部,代码逻辑通常如下:
-
开启死循环:
线程会一直活着,直到while (!bStop) { ... }bStop变为true(通常由析构函数或外部逻辑触发)。 -
数据检查(生产者-消费者模型): 线程会检查
Buff数组是否有数据。Buff:这是发送缓冲区。主线程(游戏逻辑)会把要发送的数据(比如按键信息)写入这个数组。BuffSize:记录当前缓冲区里有多少有效数据。
-
发送数据: 如果
BuffSize > 0,线程会调用 Socket 的发送函数:int32 BytesSent = 0; // 将 Buff 中的数据通过 Socket 发送出去 Socket->Send(Buff.GetData(), BuffSize, BytesSent); -
清理与休眠:
- 发送完成后,清空
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()```