【383】await async 原生平台与网页、微信小游戏效果不一致

  • 引擎版本:3.8.3

  • 问题描述:await async语法在网页、微信小游戏中运行是基于具体方法的运行耗时,当逻辑简单时基本约等于无延迟;但在Android平台上,却固定存在延迟,大概等于对应帧的所有逻辑耗时,这个要如何解决?

翻阅3.8.3源码,只找到了promise.min.js和不同平台的v8-promise.h头文件;前者貌似改动了原生平台无法生效,不确定是不是我的改法错误:

t._immediateFn = typeof setImmediate === 'function' && function (e) { setImmediate(e); } || function (e) { c(e, 0); }

promise.min.js中找到了上述语句,其中c方法是setTimeout;据此猜测是原生平台不存在setImmediate方法导致执行了setTimeout语句延迟一帧执行,因此修改为:

t._immediateFn = function (e) { e(); }

但是打包之后,Android平台依然存在明显延迟。

  • 测试代码:
	private async test() {
		let startTime = Date.now();
		await this.test1();
		let endTime = Date.now();
		console.log(`test time: ${endTime - startTime}, frame time: ${game.frameTime}`);
	}

	private async test1() {
		let startTime = Date.now();
		await this.test2();
		let endTime = Date.now();
		console.log("test1 time: ", endTime - startTime);
	}

	private async test2() {
		console.log("test2");
	}
  • 网页预览运行结果:
    B02050B3-C0A2-44a5-BF3B-C862ACB5E133

  • 微信开发者工具运行结果:
    2E7890E9-03AD-4c4f-B163-A4BDB9761120

  • Mumu模拟器Android包运行结果:
    85EFB7F7-260A-4270-9B3B-C866690BE9EC

注:游戏帧率设置为60帧。网页预览、微信开发者工具、微信小游戏预览版都稳定在0~1ms之间;Mumu模拟器及真机运行Android包波动范围较大,大约在5~17ms之间。

一台多油锅多? :joy: :joy: :joy:,静等答案

自己顶一波

目前还是没找到解决原生平台 await async 延迟的方案,直接选择弃用 await async 写法了,自己封装了个异步回调对象来代替。

一眼看过去并不能确定是 async await 的问题。万一是该平台打印耗时 15 毫秒呢?
你试试看把所有时间差先记录下来,最后结束统一打印出来。

为什么要多花时间精力去找这玩意引发的问题

嘿,你还真别说,真实业务里面走的就是记录,统一输出的。那个不方便贴出来,所以弄了个最简单的来示意 :joy:

在 JavaScript 中,await 的行为在不同平台和上下文中可能会有所不同,特别是在浏览器、Node.js 和某些特定的框架或库中。你提到的现象可能是由于以下几个原因导致的:

1. 事件循环机制

JavaScript 是单线程的,基于事件循环(Event Loop)来处理任务。async/await 实际上是基于 Promise 的语法糖,因此它的执行依赖于 Promise 的调度机制。

  • 同步代码:在 async 函数中,await 之前的代码会立即执行。
  • 异步代码await 后面的代码会在当前同步任务完成后进入微任务队列(microtask queue),等待当前任务栈清空后执行。

2. 不同平台的实现差异

不同的平台可能对事件循环有不同的优化或实现方式,这可能导致 await 的行为略有不同。

浏览器环境

在浏览器中,await 通常会在当前任务完成后立即执行后续代码,但如果有其他微任务(如 Promise.resolve().then())或宏任务(如 setTimeout),它会等待这些任务完成后再继续执行。

Node.js 环境

在 Node.js 中,await 的行为与浏览器类似,但它有一些额外的优化,尤其是在 I/O 操作中。Node.js 使用 libuv 来处理异步操作,因此某些操作可能会被调度到下一次事件循环中执行。

游戏引擎或动画框架

在一些游戏引擎(如 Unity、Cocos Creator)或动画框架(如 Three.js)中,await 可能会被调度到下一帧执行,以确保渲染和其他任务不会被阻塞。例如,在这些环境中,await 可能会与 requestAnimationFrame 或类似的机制结合使用,确保代码在下一帧执行。

3. awaitrequestAnimationFrame 的结合

如果你在某些平台上看到 await 需要等待下一帧执行,可能是因为代码中隐式或显式地使用了 requestAnimationFrame 或类似的机制。例如:

async function renderLoop() {
  await new Promise(resolve => requestAnimationFrame(resolve));
  // 这里的代码会在下一帧执行
}

4. 如何确保一致的行为

如果你希望确保 await 在所有平台上都有一致的行为,可以考虑以下几点:

  • 避免依赖具体的平台行为:尽量编写通用的异步代码,不要依赖特定平台的调度机制。
  • 使用 Promise.resolve():如果你想确保某个操作在当前任务完成后立即执行,可以使用 Promise.resolve() 来创建一个立即解决的 Promise。
await Promise.resolve();
// 这里的代码会在当前任务完成后立即执行
  • 使用 requestAnimationFrame 显式控制帧:如果你确实需要在下一帧执行代码,可以显式使用 requestAnimationFrame,而不是依赖 await 的默认行为。
await new Promise(resolve => requestAnimationFrame(resolve));
// 这里的代码会在下一帧执行

总结

await 的行为在不同平台上的确可能有所不同,主要是因为事件循环和任务调度机制的不同。为了确保一致的行为,建议避免依赖特定平台的调度机制,并根据具体需求选择合适的异步控制方式。

2赞

正常都应该按照有延迟的情况来设计代码的。。 本身就是异步实现,可以搜搜 宏任务 微任务,理解下概念

额,是按照异步设计的没毛病。问题在于直接使用 promise 之后微端和原生平台的效果不一致,导致原生平台产生比较明显的卡顿,主要解决的是这个问题。 :joy:

就15ms,你是怎么发现它不一致的

贴出来的是个示意代码,实际业务比这个复杂,加起来的延时高达好几秒,非常明显,排查了一圈才定位到这边 :joy: