【Unity Bug挑战】第12期 Awaitable 二次 await 死锁之谜 —— 为什么同一个异步操作等两次就卡死?

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 附近
  • 没有报错、没有异常、没有任何日志------就这么无声无息地死了
  • 更诡异的是:有时候第一次运行没问题,第二次进场景就必现

排查思路提示

  1. 同步上下文的角度:Unity 的 Awaitable 和 .NET Task 在 SynchronizationContext 处理上有什么不同?为什么死锁总是发生在 IL2CPP 而不是 Editor?
  2. 对象生命周期的角度:Awaitable 完成后,它的内部状态机处于什么状态?再次 await 一个已经完成的 Awaitable 会发生什么?
  3. 性能优化的角度:Unity 官方文档说 Awaitable 是"零分配"的,零分配的代价是什么?对象池复用机制会不会跟二次 await 有冲突?

#Unity #Bug挑战 #Awaitable #异步编程 #死锁 #Unity6 #对象池 #游戏开发 #C#


标签:Unity、Bug挑战、Awaitable、异步编程、死锁、Unity6、对象池、游戏开发、C#

相关推荐
探求虚无1 天前
用 AI 在 Unity 里做一款 RTS:最难的不是生成代码,而是证明它真的能玩
人工智能·unity·游戏引擎
海拥✘1 天前
让 AI 修 AI 平台自己的 Bug:我用 SoduWork 给 SoduWork 提了一个 PR
人工智能·bug
huaqianzkh1 天前
WinForm C#:循环修改控件UI闪烁卡顿?SuspendLayout / ResumeLayout 与 BeginUpdate 优化
开发语言·ui·c#
用户3721574261352 天前
C# 实现 Excel 单元格保护:锁定公式并允许编辑指定区域
c#·ai编程·编程语言
17岁的勇气2 天前
Unity Shader unity文档学习笔记(二十二):shader变体
笔记·学习·unity
公子小六2 天前
基于.NET的Windows窗体编程之WinForms数据持久化(1)
windows·microsoft·c#·.net·winforms
三雷科技2 天前
第6章第4节:自定义分词器
开发语言·c#
zhanghaha13142 天前
C# 系列教程_12 字符串 string 保姆级系统教程(面向新手,大量示例 + 原理讲解)
开发语言·数据库·c#
Coder_Shenshen3 天前
罗克韦尔ACD工程归档离线变量树解析
算法·c#