整站网站优化运营加强学院网站建设
❌WebGL 平台未捕获的 OperationCanceledException 会报红
abort() 报错,通常是 WebAssembly 在运行时被强制中断,在 WebGL 下常见原因:
取消
UniTask时没有正确捕获异常,导致 JS 抛出abort。场景跳转过程中 还有未完成的异步任务,WebGL 在切换
Scene时无法安全执行,触发 wasm 异常。WebGL 对
CancellationToken和Thread的支持有限,不能像 PC 端一样直接取消任务。1️⃣ WebGL 下 abort 的常见原因
CancellationToken 触发的 UniTask 抛异常
.SuppressCancellationThrow()可以避免这一类。SceneManager.LoadSceneAsync与正在执行的异步任务冲突
在 WebGL 下,如果有未完成的 UniTask 任务在切换场景时仍然运行,尤其是含有await的方法,Unity 会直接 abort。动画或点击回调中包含 await 并且被取消
如果OnClickAsync3D内部含有await UniTask.Delay(cancellationToken),并且同时触发了场景切换,也可能触发 abort。你现在的 WebGL 报错信息显示的是:
Uncaught RuntimeError: unreachable这不是 UniTask 或场景切换的问题,而是 Unity WebGL 在运行时遇到了异常,直接触发了 abort(),导致整个 wasm 中断。
这种情况通常不是由.SuppressCancellationThrow()引起的,而是下面几个原因之一:2️⃣ 使用了已经被销毁或 null 的对象
在 WebGL 上,如果你在异步任务中访问了已经被销毁的 GameObject、组件或 Animation Clip,就会直接 abort。
SuppressCancellationThrow()只抑制异常,不会阻止访问已经销毁的对象。如果点击或动画回调触发时对象被销毁,会直接报
unreachable。await UniTask.Delay(…); // 此时 YaoPing 或 YaoPing.GetComponent<Animation>().clip 可能已经 nullWebGL 会立即崩溃,而 PC 平台可能只是报空引用。
检查点:
if (YaoPing == null || YaoPing.GetComponent<Animation>() == null) return; // 或跳过3️⃣ Animation clip 长度计算为负值或异常
你目前写:
TimeSpan.FromSeconds(YaoPing.GetComponent<Animation>().clip.length - 11)如果 clip.length < 11,会得到负值,WebGL 不允许负延迟,会直接 abort。
修复示例:
var clipLength = Mathf.Max(YaoPing.GetComponent<Animation>().clip.length - 11, 0f); await UniTask.Delay(TimeSpan.FromSeconds(clipLength), cancellationToken) .SuppressCancellationThrow();4️⃣ 异步事件在场景跳转前未取消
即使你调用:
UniTaskManager.CancelAllTasks(); SafeLoadSceneAsync(...);如果之前的 await YaoPing.OnClickAsync3D(...) 还在继续,WebGL 会在访问被销毁对象时 abort。
✅如何避免:
非空检查:
YaoPing,fenMo,guanBi,outlinable,anim等。所有 await 都加
.SuppressCancellationThrow()。点击等待:
OnClickAsync3D内部无需改动,外层 try-catch 防止 WebGL crash。场景切换安全,延迟一帧后调用。
📌关于xxx实验发布Webgl后从场景中点击返回时报错的原因和解决方法:
❌原因:
- 调用返回菜单时,没有取消UniTask的异步方法报错
- 异步方法未加上
.SuppressCancellationThrow()报错。 - UniTask自带的方法或许不用加,但是自定义的UniTask方法需要加上,否则会报错。
✅解决方法:
- 调用返回菜单时,返回按钮的方法中添加UniTask的异步取消方法:CancelAllTasks();
异步方法体本身使用:完全安全的 WebGL 版本,所有 await 都加
.SuppressCancellationThrow()。
下列是完全安全的 WebGL 版本。
public GameObject ChengLiangZhi;public GameObject YaoPing;public GameObject fenMo;public GameObject guanBi;private void Start(){var cts = UniTaskManager.CreateIdTask();OperationEvent(cts.cts.Token).Forget();//OperationEventSafe(cts.cts.Token).Forget();guanBi.GetComponent<Button>().AddListener(() =>{UniTaskManager.CancelAllTasks();// 安全切换场景SafeLoadSceneAsync("第二级菜单").Forget();});}/// <summary>/// WebGL 安全场景加载/// </summary>private async UniTask SafeLoadSceneAsync(string sceneName){await UniTask.DelayFrame(1).SuppressCancellationThrow();var op = SceneManager.LoadSceneAsync(sceneName);if (op != null){await op.ToUniTask().SuppressCancellationThrow();}}/// <summary>/// WebGL 安全操作流程/// </summary>public async UniTask OperationEventSafe(CancellationToken ct){HintQuickChangeScripts.QuicklyChangeTextAction("称量合适分量的LB培养基粉末到锥形瓶中", false);await UniTask.NextFrame(ct);await UniTask.Delay(TimeSpan.FromSeconds(2), cancellationToken: ct);// 安全开启描边SafeEnableOutlinable(YaoPing, true);await SetDelay(ct).SuppressCancellationThrow();// 安全点击//await SafeClick3D(YaoPing, ct);//await SafeClick(YaoPing, ct);//await YaoPing.OnClickAsync3D(ct).SafeAwait(ct);//await SafeCall(YaoPing.OnClickAsync3D(ct));await YaoPing.OnClickAsync3D(ct).SafeAwait(ct);// // 安全关闭描边SafeEnableOutlinable(YaoPing, false); //报错//// // 安全播放动画SafePlayAnimation(YaoPing, "药瓶打开");// 安全延迟await SafeDelaySeconds(11f, ct);// 安全激活粉末if (fenMo != null) fenMo.gameObject.SetActive(true);// 延迟剩余动画时间float animLength = Mathf.Max(YaoPing.GetComponent<Animation>()?.clip.length - 11f ?? 0f, 0f);await SafeDelaySeconds(animLength, ct);}/// <summary>/// 不报错的脚本方法/// </summary>/// <param name="cancellationToken"></param>public async UniTask OperationEvent(CancellationToken cancellationToken){HintQuickChangeScripts.QuicklyChangeTextAction("称量合适分量的LB培养基粉末到锥形瓶中", false);var outlinable = YaoPing.transform.GetChild(0).GetComponent<Outlinable>();outlinable.enabled = true;// 点击 3D 物体(WebGL 安全)//await YaoPing.OnClickAsync3D(cancellationToken).SuppressCancellationThrow();await YaoPing.OnClickAsync3D(cancellationToken).SafeAwait(cancellationToken);outlinable.enabled = false;// 播放动画AnimationQuicklyPlayScript.AnimationFastPlayAction(YaoPing, false, "药瓶打开");// 延迟动画前半段await UniTask.Delay(TimeSpan.FromSeconds(11), cancellationToken: cancellationToken).SuppressCancellationThrow();fenMo.gameObject.SetActive(true);// 延迟剩余动画时间(避免负数)var animationLength = Mathf.Max(YaoPing.GetComponent<Animation>().clip.length - 11, 0f);await UniTask.Delay(TimeSpan.FromSeconds(animationLength), cancellationToken: cancellationToken).SuppressCancellationThrow();}#region WebGL 安全工具方法private static void SafeEnableOutlinable(GameObject obj, bool enable){if (obj == null || !obj.activeInHierarchy){return;}var outlinable = obj.transform.GetChild(0)?.GetComponent<Outlinable>();if (outlinable != null) outlinable.enabled = enable;}private static async UniTask SafeClick3D(GameObject obj, CancellationToken ct){if (obj == null || !obj.activeInHierarchy) return;await obj.OnClickAsync3D(ct).SuppressCancellationThrow();await UniTask.NextFrame(ct);}public static async UniTask SafeClick(GameObject obj, CancellationToken ct){await obj.OnClickAsync3D(ct).SuppressCancellationThrow();}private static void SafePlayAnimation(GameObject obj, string clipName){if (obj == null || !obj.activeInHierarchy) return;AnimationQuicklyPlayScript.AnimationFastPlayAction(obj, false, clipName);}private static async UniTask SafeDelaySeconds(float seconds, CancellationToken ct){if (seconds <= 0f) return;await UniTask.Delay(TimeSpan.FromSeconds(seconds), cancellationToken: ct).SuppressCancellationThrow();}private static async UniTask SafeCall(UniTask task){await task.SuppressCancellationThrow();}private static async UniTask SetDelay(CancellationToken ct){await UniTask.Delay(TimeSpan.FromSeconds(2), cancellationToken: ct);}#endregion
执行方法:OperationEvent(ct).Forget();