Awaitable 二次 await 死锁之谜 ------ 为什么同一个异步操作等两次就卡死?
栏目:Unity Bug挑战 | 期号:NO.012 | 日期:2026-10-31 | 4星 · 中高级 | Bug挑战 | Unity 6+ · 异步编程 / Awaitable / 对象池
Bug 现场
你正在把项目从 UniTask 迁移到 Unity 6 原生的 Awaitable。项目里有一个资源预加载管理器,需要在多个地方等待同一个加载操作完成。你觉得这很合理------就像 Task 一样,同一个 Awaitable 等多次又能怎么样呢?
using UnityEngine;
using System.Collections.Generic;
public class PreloadManager : MonoBehaviour
{
// 缓存正在进行的预加载操作,多个调用方共享等待
private Dictionary<string, Awaitable> _loadingOperations = new();
public async Awaitable PreloadAssetAsync(string address)
{
if (_loadingOperations.TryGetValue(address, out var existingOp))
{
// 已经在加载了,共享同一个 Awaitable,一起等
await existingOp; // ← 第二次 await 同一个 Awaitable
return;
}
// 第一次加载,启动异步操作
var op = LoadAssetInternalAsync(address);
_loadingOperations[address] = op;
await op; // ← 第一次 await
_loadingOperations.Remove(address);
}
private async Awaitable LoadAssetInternalAsync(string address)
{
// 模拟资源加载:等待3帧
for (int i = 0; i < 3; i++)
{
await Awaitable.NextFrameAsync();
}
Debug.Log($"[Preload] Asset {address} loaded");
}
}
调用方代码:
public class GameBootstrap : MonoBehaviour
{
public PreloadManager Preloader;
async void Start()
{
// 两个系统同时请求预加载同一个资源
var task1 = Preloader.PreloadAssetAsync("UI/CommonAtlas");
var task2 = Preloader.PreloadAssetAsync("UI/CommonAtlas");
await Awaitable.WhenAll(task1, task2); // 等两个都完成
Debug.Log("Bootstrap complete!");
}
}
诡异现象
预期行为:
- Editor 下运行一切正常,"Bootstrap complete!" 如期打印
- 两个调用方共享同一个加载操作,资源只加载一次
- 跟 Task / UniTask 的行为一致 实际现象:
- Editor 下 Mono 模式:大概率正常,偶尔会卡死一帧然后恢复
- IL2CPP 打包后(Android/iOS/PC Standalone):直接死锁,游戏完全卡住
- 卡死时 Profiler 显示主线程 100% 占用,堆栈停在
Awaitable.GetResult附近 - 没有报错、没有异常、没有任何日志------就这么无声无息地死了
- 更诡异的是:有时候第一次运行没问题,第二次进场景就必现
排查思路提示
- 同步上下文的角度:Unity 的 Awaitable 和 .NET Task 在 SynchronizationContext 处理上有什么不同?为什么死锁总是发生在 IL2CPP 而不是 Editor?
- 对象生命周期的角度:Awaitable 完成后,它的内部状态机处于什么状态?再次 await 一个已经完成的 Awaitable 会发生什么?
- 性能优化的角度:Unity 官方文档说 Awaitable 是"零分配"的,零分配的代价是什么?对象池复用机制会不会跟二次 await 有冲突?
#Unity #Bug挑战 #Awaitable #异步编程 #死锁 #Unity6 #对象池 #游戏开发 #C#
标签:Unity、Bug挑战、Awaitable、异步编程、死锁、Unity6、对象池、游戏开发、C#