上一节介绍了资源的异步加载。实际项目中,一个流程往往不只等待一个资源,例如进入战斗前可能需要同时加载配置、角色 Prefab 和场景资源。
如果在每个回调中手动判断其他任务是否完成,代码很快会变得混乱。MyFramework 提供了 AsyncTaskGroup,用于在多个异步任务全部完成后统一执行后续逻辑。
项目地址:
一、多个异步回调的问题
假设进入场景前需要完成三个任务:
bash
加载地图配置
预加载玩家 Prefab
预加载怪物 Prefab
直接使用多个布尔值:
bash
protected bool mMapLoaded;
protected bool mPlayerLoaded;
protected bool mMonsterLoaded;
protected void checkLoadFinished()
{
if (!mMapLoaded ||
!mPlayerLoaded ||
!mMonsterLoaded)
{
return;
}
enterBattle();
}
每个回调都要修改状态并再次检查:
bash
mMapLoaded = true;
checkLoadFinished();
任务数量增加后,还要继续增加字段和判断。流程中途退出时,也容易遗留旧状态。
AsyncTaskGroup 可以把这些等待关系集中起来。
二、创建 AsyncTaskGroup
通过框架中的 AsyncTaskGroupManager 创建任务组:
bash
AsyncTaskGroup group = mAsyncTaskGroupManager.createGroup(onPrepareFinished);
然后把需要等待的异步操作加入任务组:
bash
group.addTask(operation);
当组内所有任务完成后,框架会自动调用:
bash
onPrepareFinished();
任务组完成后也会由管理器自动销毁,不需要手动回收。
三、等待多个资源加载
下面同时加载地图配置和两个 Prefab:
bash
using UnityEngine;
using static FrameBaseHotFix;
public class MainSceneLoading : SceneProcedure
{
protected ResourceRef<TextAsset> mMapConfigRef;
protected bool mLoadFailed;
protected override void onInit(
SceneProcedure lastProcedure)
{
mLoadFailed = false;
AsyncTaskGroup group =
mAsyncTaskGroupManager.createGroup(
onPrepareFinished);
group.addTask(
mResourceManager
.loadGameResourceAsyncSafe<TextAsset>(
this,
"Config/BattleMap.json",
onMapConfigLoaded));
group.addTask(
mPrefabPoolManager.initObjectToPoolAsync(
"Character/Player.prefab",
0,
1,
true,
null));
group.addTask(
mPrefabPoolManager.initObjectToPoolAsync(
"Character/Monster.prefab",
0,
10,
true,
null));
}
protected void onMapConfigLoaded(
ResourceRef<TextAsset> configRef)
{
if (configRef == null)
{
mLoadFailed = true;
return;
}
mMapConfigRef = configRef;
}
protected void onPrepareFinished()
{
if (mLoadFailed)
{
logError("战斗准备失败");
return;
}
changeProcedure<MainSceneBattle>();
}
protected override void onExit(
SceneProcedure nextProcedure)
{
mResourceManager?.unload(
ref mMapConfigRef);
}
}
这里三个异步操作会先分别开始执行。
AsyncTaskGroup 每帧检查它们的完成状态,只有全部结束后才调用 onPrepareFinished()。
四、任务结果仍然由各自回调处理
AsyncTaskGroup 只负责:
bash
等待全部任务结束
统一触发完成回调
自动销毁任务组
它不会判断每个任务是否成功,也不会保存任务结果。
因此,各个任务仍然要在自己的回调中处理结果:
bash
protected void onMapConfigLoaded(
ResourceRef<TextAsset> configRef)
{
if (configRef == null)
{
mLoadFailed = true;
return;
}
mMapConfigRef = configRef;
}
可以把职责理解为:
bash
单个任务回调
负责接收数据、保存引用和记录失败状态
任务组回调
负责确认所有任务都已结束,并进入下一流程
"任务完成"和"任务成功"不是同一个概念。
五、空任务不会影响结果
addTask() 会忽略 null:
bash
group.addTask(null);
因此,某些可选任务没有创建异步操作时,不会导致任务组一直等待。
空任务组会在下一次检查时立即完成并调用回调。
不过业务代码仍然应该检查异步接口为什么返回 null,避免把参数错误误认为正常完成。
六、AsyncTaskGroup 不是协程启动器
AsyncTaskGroup 保存的是:
bash
IEnumerator
CustomAsyncOperation 继承自 CustomYieldInstruction,因此可以直接加入任务组:
bash
CustomAsyncOperation operation =
mResourceManager.loadGameResourceAsync<Texture>(
"UI/Loading.png",
onTextureLoaded);
group.addTask(operation);
需要注意,任务组的核心职责是检查任务是否结束,不是代替 Unity 启动任意协程。
最适合加入任务组的是已经开始执行、能够通过 MoveNext() 查询完成状态的异步操作。
七、中途取消等待
任务组正常完成后会自动销毁。
只有业务流程提前结束,不再关心这些任务时,才需要手动销毁:
bash
protected AsyncTaskGroup mPrepareGroup;
protected override void onInit(
SceneProcedure lastProcedure)
{
mPrepareGroup =
mAsyncTaskGroupManager.createGroup(
onPrepareFinished);
}
protected override void onExit(
SceneProcedure nextProcedure)
{
mAsyncTaskGroupManager?.destroyGroup(
mPrepareGroup);
mPrepareGroup = null;
}
手动销毁任务组只表示:
bash
不再等待这些任务
不再执行任务组完成回调
它不会自动取消已经开始的资源加载或其他异步操作。
这些任务完成后产生的数据,仍然需要由各自的安全回调和资源管理逻辑处理。
八、返回一个可等待的组合操作
有些函数不是设置回调,而是需要返回一个可以继续 yield return 的操作,这时可以使用:
bash
CustomMultiAsyncOperation
例如:
bash
public CustomAsyncOperation preloadBattle()
{
CustomMultiAsyncOperation multi = new();
multi.addOperation(
mPrefabPoolManager.initObjectToPoolAsync(
"Character/Player.prefab",
0,
1,
true,
null));
multi.addOperation(
mPrefabPoolManager.initObjectToPoolAsync(
"Character/Monster.prefab",
0,
10,
true,
null));
return multi;
}
外部可以等待:
bash
yield return preloadBattle();
只有所有子操作都不再等待时,CustomMultiAsyncOperation 才会完成。
九、两种组合方式如何选择
AsyncTaskGroup 和 CustomMultiAsyncOperation 都能等待多个操作,但使用方式不同。
AsyncTaskGroup
适合完成后执行回调:
bash
AsyncTaskGroup group =
mAsyncTaskGroupManager.createGroup(
onAllFinished);
特点:
bash
由 AsyncTaskGroupManager 每帧检查
完成后调用 Action
自动销毁任务组
适合任意业务时刻使用
CustomMultiAsyncOperation
适合从函数中返回异步操作:
bash
return multiOperation;
特点:
bash
自身就是一个可等待操作
适合 yield return
不负责完成回调
由调用方决定如何等待
选择标准可以概括为:
bash
完成后直接执行一段逻辑
使用 AsyncTaskGroup
需要把多个任务包装成一个返回值
使用 CustomMultiAsyncOperation
十、总结
使用 AsyncTaskGroup 的基本流程是:
bash
AsyncTaskGroup group =
mAsyncTaskGroupManager.createGroup(
onAllFinished);
group.addTask(operationA);
group.addTask(operationB);
group.addTask(operationC);
它把原本分散的多个完成标记,变成一个清晰的等待关系:
bash
启动多个异步任务
↓
各自处理加载结果
↓
AsyncTaskGroup 等待全部结束
↓
统一进入下一流程
AsyncTaskGroup 负责协调完成时机,单个任务回调负责处理数据和失败状态。将这两部分职责分开后,场景准备、资源预加载和批量初始化等流程会更加清晰。