20 KiB
OperationSystem 异步操作系统
模块概述
OperationSystem 是 YooAsset 资源管理系统的异步操作调度核心,负责管理所有异步操作的生命周期、调度执行和状态追踪。该模块提供了统一的异步操作抽象,支持协程、async/await、回调等多种异步编程模式。
核心职责
- 异步操作的统一抽象和生命周期管理
- 基于优先级的操作调度
- 时间切片执行(防止主线程阻塞)
- 多种异步编程模式支持
- 操作状态追踪和调试信息收集
设计目标
| 目标 | 说明 |
|---|---|
| 统一抽象 | 所有异步操作继承同一基类,接口一致 |
| 灵活调度 | 支持优先级排序、时间切片、帧预算控制 |
| 多模式支持 | 协程(IEnumerator)、Task(async/await)、回调 |
| 可调试性 | 完整的状态追踪、耗时统计、层级关系 |
| 线程安全 | 所有调度逻辑在主线程执行 |
架构概念
系统架构
┌─────────────────────────────────────────────────────────┐
│ 上层调用者 │
│ (ResourceManager / FileSystem / 业务层) │
└─────────────────────────┬───────────────────────────────┘
│ StartOperation()
┌─────────────────────────▼───────────────────────────────┐
│ OperationSystem │
│ (调度器) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 优先级队列 │ │ 时间切片 │ │ 回调通知 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────┬───────────────────────────────┘
│ UpdateOperation()
┌─────────────────────────▼───────────────────────────────┐
│ AsyncOperationBase │
│ (操作基类) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 状态机 │ │ 子任务管理 │ │ 异步模式 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
核心组件
- OperationSystem: 静态调度器,管理所有操作的执行
- OperationScheduler: 包裹级调度器,维护操作队列并负责更新调度
- AsyncOperationBase: 异步操作基类,定义生命周期和状态
- EOperationStatus: 操作状态枚举
文件结构
OperationSystem/
├── EOperationStatus.cs # 操作状态枚举
├── AsyncOperationBase.cs # 异步操作基类
├── OperationScheduler.cs # 包裹级调度器
├── OperationSystem.cs # 异步操作调度器
枚举定义
EOperationStatus(操作状态)
public enum EOperationStatus
{
None, // 未开始
Processing, // 处理中
Succeed, // 已成功
Failed // 已失败
}
状态转换:
StartOperation() InternalUpdate()
None ─────────────────► Processing ─────────────────┬──► Succeed
│
└──► Failed
核心类说明
AsyncOperationBase(异步操作基类)
所有异步操作的抽象基类,实现了 IEnumerator 和 IComparable<AsyncOperationBase> 接口。
公共属性
| 属性 | 类型 | 说明 |
|---|---|---|
Priority |
uint |
任务优先级(值越大越优先) |
Status |
EOperationStatus |
当前状态 |
Error |
string |
错误信息(失败时) |
Progress |
float |
处理进度(0-1) |
IsDone |
bool |
是否已完成(Succeed 或 Failed) |
Task |
Task |
用于 async/await |
BeginTime |
string |
开始时间(调试用) |
ProcessTime |
long |
处理耗时毫秒(调试用) |
说明:
AsyncOperationBase本身不保存包裹名称;包裹名称由OperationSystem.StartOperation(packageName, operation)传入,并由OperationScheduler维护。
内部协作:时间切片(IsBusy)
为配合 OperationSystem.MaxTimeSlice 的时间切片预算,AsyncOperationBase 提供了内部属性 IsBusy(internal)用于任务在 InternalUpdate() 内主动让出本帧预算。
- 推荐用法:在
InternalUpdate()内部(或内部子步骤)在执行重逻辑前判断IsBusy,若繁忙则return,把工作拆到下一帧继续执行。 - 同步等待特殊处理:当调用了
WaitForAsyncComplete()进入同步等待阶段时,IsBusy会强制返回false,避免因时间切片判断导致同步等待无法推进。 - 注意:
WaitForAsyncComplete()会阻塞主线程,应谨慎使用;同步等待阶段不受时间切片保护,可能带来卡顿。
公共事件
/// <summary>
/// 完成事件(支持后注册立即触发)
/// </summary>
public event Action<AsyncOperationBase> Completed;
公共方法
/// <summary>
/// 同步等待异步操作完成
/// </summary>
public void WaitForAsyncComplete();
内部抽象方法(子类实现)
| 方法 | 说明 |
|---|---|
InternalStart() |
操作开始时调用 |
InternalUpdate() |
每帧更新时调用 |
InternalAbort() |
操作中止时调用(可选) |
InternalWaitForAsyncComplete() |
同步等待时调用(可选) |
InternalGetDesc() |
获取操作描述(可选) |
子任务管理
// 添加/移除子任务(内部使用)
internal void AddChildOperation(AsyncOperationBase child);
internal void RemoveChildOperation(AsyncOperationBase child);
调用约束(重要):
- 仅允许在 Unity 主线程调用(与
OperationSystem.Update()的调度线程一致)。 - 不要在
InternalUpdate()正在遍历/处理中途频繁增删子任务;推荐在任务启动阶段完成子任务挂接,或在确保无并发修改风险的安全点调整。 AbortOperation()会递归中止子任务,子任务的生命周期由父任务统一管理;避免在Completed回调里再去修改子任务关系,防止时序混乱。
OperationSystem(调度器)
静态类,负责异步操作的调度和管理。
配置属性
/// <summary>
/// 每帧最大执行时间(毫秒)
/// 默认值:long.MaxValue(无限制)
/// </summary>
public static long MaxTimeSlice { set; get; }
/// <summary>
/// 当前帧是否已超时
/// </summary>
public static bool IsBusy { get; }
核心方法
/// <summary>
/// 初始化异步操作系统
/// </summary>
public static void Initialize();
/// <summary>
/// 每帧更新(由 YooAssets 驱动)
/// </summary>
public static void Update();
/// <summary>
/// 销毁所有操作
/// </summary>
public static void DestroyAll();
/// <summary>
/// 清理指定包裹的所有操作
/// </summary>
public static void ClearPackageOperation(string packageName);
/// <summary>
/// 启动异步操作
/// </summary>
public static void StartOperation(string packageName, AsyncOperationBase operation);
/// <summary>
/// 设置调度器优先级
/// </summary>
public static void SetSchedulerPriority(string packageName, uint priority);
/// <summary>
/// 获取调度器优先级
/// </summary>
public static uint GetSchedulerPriority(string packageName);
包裹调度说明
packageName不允许为空(null/""),否则会抛出异常。- 若需要使用全局调度器,请传入
OperationSystem.GLOBAL_SCHEDULER_NAME(Initialize()时自动创建)。 packageName为非全局调度器名称时,必须先通过YooAssets.CreatePackage(packageName)创建包裹(内部会注册对应OperationScheduler),否则会抛出异常。
回调监听
OperationSystem 当前未提供全局任务开始/结束回调的注册接口。
如需监听任务结束(推荐),请直接订阅具体任务的 Completed 事件:
var operation = package.LoadAssetAsync<GameObject>(location);
operation.Completed += op =>
{
// TODO : 根据 op.Status 判断成功/失败
};
异步编程模式
1. 协程模式(IEnumerator)
IEnumerator LoadAsset()
{
var operation = package.LoadAssetAsync<GameObject>("Assets/Prefab.prefab");
yield return operation;
if (operation.Status == EOperationStatus.Succeed)
{
GameObject prefab = operation.AssetObject as GameObject;
}
}
2. Task 模式(async/await)
async Task LoadAssetAsync()
{
var operation = package.LoadAssetAsync<GameObject>("Assets/Prefab.prefab");
await operation.Task;
if (operation.Status == EOperationStatus.Succeed)
{
GameObject prefab = operation.AssetObject as GameObject;
}
}
3. 回调模式(Completed 事件)
void LoadAsset()
{
var operation = package.LoadAssetAsync<GameObject>("Assets/Prefab.prefab");
operation.Completed += OnLoadCompleted;
}
void OnLoadCompleted(AsyncOperationBase op)
{
var operation = op as AssetHandle;
if (operation.Status == EOperationStatus.Succeed)
{
GameObject prefab = operation.AssetObject as GameObject;
}
}
4. 同步等待模式
void LoadAssetSync()
{
var operation = package.LoadAssetAsync<GameObject>("Assets/Prefab.prefab");
operation.WaitForAsyncComplete(); // 阻塞等待完成
if (operation.Status == EOperationStatus.Succeed)
{
GameObject prefab = operation.AssetObject as GameObject;
}
}
调度机制
优先级调度
操作按 Priority 属性降序排列,优先级高的操作先执行。
操作优先级
var operation = package.LoadAssetAsync<GameObject>(location);
operation.Priority = 100; // 设置高优先级
包裹优先级
通过 ResourcePackage.PackagePriority 可以设置包裹的调度器优先级,值越大越优先更新。
// 创建包裹时指定优先级
var package = YooAssets.CreatePackage("MyPackage", 100);
// 运行时动态调整优先级
package.PackagePriority = 200;
// 获取当前优先级
uint priority = package.PackagePriority;
使用场景:
- 多包裹场景下,可根据游戏状态动态调整包裹优先级
- 例如:进入战斗时提高战斗资源包的优先级,退出战斗时恢复默认优先级
排序规则:
- 新操作添加时:若新增队列存在非零优先级,则触发排序
- 运行中修改
Priority:调度器会在每帧Update()的排序阶段检测IsDirty并触发重排;若在某个操作的InternalUpdate()内修改(本帧排序已完成),则新的优先级会延后一帧生效(可能与预期不符) - 若期望本帧生效:请尽量在任务入队前或本帧调度器
Update()开始前设置Priority,避免在InternalUpdate()内临时调整 - 排序使用
List.Sort()进行原地排序;频繁修改优先级会带来额外排序开销,建议按需使用
时间切片
通过 MaxTimeSlice 控制每帧最大执行时间,防止主线程阻塞。
// 设置每帧最多执行 8 毫秒
OperationSystem.MaxTimeSlice = 8;
操作侧协作建议:
- 在操作的
InternalUpdate()中使用IsBusy(AsyncOperationBase的内部属性)进行“自愿让出”,将重任务拆分到多帧执行。 - 在同步等待(
WaitForAsyncComplete())阶段,IsBusy会强制返回false,以保证同步等待推进;此时需要自行评估卡顿风险。
执行流程:
每帧 Update()
│
├── 记录帧开始时间 _frameTime
│
└── 遍历操作队列
│
├── 检查 IsBusy(是否超时)
│ │
│ └── 超时则中断本帧
│
└── 执行 operation.UpdateOperation()
操作生命周期
┌─────────────────────────────────────────────────────────────────┐
│ 操作生命周期 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 创建操作 │
│ └── Status = None │
│ │
│ 2. StartOperation() │
│ ├── Status = Processing │
│ ├── DebugBeginRecording() │
│ ├── InternalStart() │
│ └── 添加到 _newList │
│ │
│ 3. Update() - 每帧调度 │
│ ├── 移除已完成操作 │
│ ├── 合并 _newList 到 _operations │
│ ├── 按优先级排序(如需要) │
│ └── 遍历执行 UpdateOperation() │
│ │
│ 4. UpdateOperation() │
│ ├── DebugUpdateRecording() │
│ ├── InternalUpdate() │
│ └── 检查 IsDone │
│ │ │
│ └── 完成时: │
│ ├── IsFinish = true │
│ ├── Progress = 1f │
│ ├── DebugEndRecording() │
│ ├── 触发 Completed 回调 │
│ └── 设置 TaskCompletionSource │
│ │
│ 5. 下一帧移除完成的操作 │
│ │
└─────────────────────────────────────────────────────────────────┘
调试支持
调试信息结构
[Serializable]
internal struct DebugOperationInfo
{
public string OperationName; // 任务名称
public string OperationDesc; // 任务说明
public uint Priority; // 优先级
public float Progress; // 任务进度
public string BeginTime; // 任务开始的时间
public long ProcessTime; // 处理耗时(单位:毫秒)
public string Status; // 任务状态
public List<DebugOperationInfo> Childs; // 子任务列表(注意:JsonUtility 序列化深度限制)
}
说明:该结构体真实定义位于
Runtime/DiagnosticSystem/DebugOperationInfo.cs,这里仅展示关键字段以便理解。
获取调试信息
// 获取指定包裹的所有操作信息(内部调试接口)
// packageName 不允许为空;全局调度器请使用 OperationSystem.GLOBAL_SCHEDULER_NAME
// 非全局包裹需先 YooAssets.CreatePackage(packageName)
var infos = OperationSystem.GetDebugOperationInfos(OperationSystem.GLOBAL_SCHEDULER_NAME);
foreach (var info in infos)
{
Debug.Log($"{info.OperationName}: {info.Status}, {info.ProcessTime}ms");
}
耗时统计
在 DEBUG 模式下自动统计操作耗时:
// 操作完成后可获取耗时
Debug.Log($"开始时间: {operation.BeginTime}");
Debug.Log($"处理耗时: {operation.ProcessTime}ms");
设计模式
模板方法模式
AsyncOperationBase 定义算法骨架,子类实现具体步骤:
AsyncOperationBase
│
├── StartOperation() ──► InternalStart() [子类实现]
├── UpdateOperation() ──► InternalUpdate() [子类实现]
├── AbortOperation() ──► InternalAbort() [子类实现]
└── WaitForAsyncComplete() ──► InternalWaitForAsyncComplete() [子类实现]
状态机模式
操作状态由 EOperationStatus 管理:
┌──────┐ StartOperation() ┌────────────┐ UpdateOperation() ┌─────────┐
│ None │ ─────────────────► │ Processing │ ──────────────────► │ Succeed │
└──────┘ └────────────┘ └─────────┘
│
│ UpdateOperation() / AbortOperation()
▼
┌──────────┐
│ Failed │
└──────────┘
组合模式
通过内部子任务列表支持父子操作关系:
ParentOperation
├── ChildOperation1
├── ChildOperation2
└── ChildOperation3
└── GrandChildOperation
类继承关系
IEnumerator + IComparable<AsyncOperationBase>
│
▼
AsyncOperationBase (抽象基类)
│
└── [YooAsset 内部操作]
│
├── InitializationOperation
├── LoadAssetOperation
├── LoadSceneOperation
├── DownloadOperation
└── ...
注意事项
- 主线程执行:所有操作的调度和更新都在 Unity 主线程执行
- 时间切片:设置合理的
MaxTimeSlice避免卡顿(建议 8-16ms) - 同步等待:
WaitForAsyncComplete()会阻塞主线程,谨慎使用 - 子任务中止:父操作中止时会自动中止所有子操作
- 回调异常:
Completed回调中的异常会被捕获并记录,不会中断系统 - 编辑器重置:编辑器中使用
RuntimeInitializeOnLoadMethod自动重置状态 - 循环保护:在
InternalWaitForAsyncComplete()中建议使用RunBatchExecution()(默认 1000 次)限制单次推进次数,避免陷入无限循环或长时间占用主线程