Unity MyFramework 用法说明(十六):使用 AsyncTaskGroup 等待多个异步任务完成

上一节介绍了资源的异步加载。实际项目中,一个流程往往不只等待一个资源,例如进入战斗前可能需要同时加载配置、角色 Prefab 和场景资源。

如果在每个回调中手动判断其他任务是否完成,代码很快会变得混乱。MyFramework 提供了 AsyncTaskGroup,用于在多个异步任务全部完成后统一执行后续逻辑。

项目地址:

github.com/ZHOURUIH/My...

一、多个异步回调的问题

假设进入场景前需要完成三个任务:

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 才会完成。

九、两种组合方式如何选择

AsyncTaskGroupCustomMultiAsyncOperation 都能等待多个操作,但使用方式不同。

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 负责协调完成时机,单个任务回调负责处理数据和失败状态。将这两部分职责分开后,场景准备、资源预加载和批量初始化等流程会更加清晰。

相关推荐
_zhourui_h_3 小时前
Unity MyFramework 用法说明(十七):使用 WaitingManager 统一等待异步任务与业务条件
unity3d
SmalBox10 小时前
【节点】[UVCombine节点]原理解析与实际应用
unity3d·游戏开发·图形学
_zhourui_h_1 天前
Unity MyFramework 用法说明(十五):使用 ResourceRef 管理资源引用与异步加载
unity3d
SmalBox1 天前
【节点】[ThreadMapDetail节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox2 天前
【节点】[Emission节点]原理解析与实际应用
unity3d·游戏开发·图形学
_zhourui_h_2 天前
Unity MyFramework 用法说明(十四):使用 ComponentOwner 和 GameComponent 拆分对象能力
unity3d
_zhourui_h_3 天前
Unity MyFramework 用法说明(十三):使用 StateMachine 管理互斥与共存的角色状态
unity3d
SmalBox3 天前
【节点】[UnpackWaterData节点]原理解析与实际应用
unity3d·游戏开发·图形学