返回博客首页
← 所有文章

在 NativeScript 中使用 Worker 在后台线程上执行 JavaScript

2016 年 10 月 19 日 — 作者:Ivan Buhov

引言

NativeScript 2.4 版本即将发布,但我们迫不及待地想要分享这个好消息。 支持在后台线程上执行 JavaScriptNativeScript 想法门户网站中投票数最多的功能之一,现在它终于实现了。如果您不想等待 NativeScript 2.4 正式发布日期来测试 worker,可以通过 运行最新版本的 NativeScript 进行尝试。Worker 支持仍处于实验阶段,并且存在一些 限制,因此您的反馈和建议非常受欢迎。您可以在 此处(Android)和 此处(iOS)分享您的想法。

什么是 worker?

从本质上讲,worker 是在后台运行的脚本。Worker 在自己的线程中运行,隔离在单独的全局上下文中,允许您执行长时间运行的密集任务,而不会对您花费大量时间构建的流畅 UI 产生负面影响。NativeScript worker 的实现松散地基于 W3C Web Workers 规范,但并不完全符合它,因为规范的很大一部分仅在浏览器的上下文中才有意义。

如何使用 worker?

既然您已经了解了 worker 的相关知识,现在是时候向您展示一个简短的示例了

var worker = new Worker("./workers/myWorker");


以上代码将设置一个新线程,初始化一个新的运行时实例并执行脚本文件。接下来,您可能希望与您的 worker 交互,或者接收来自它的结果。数据通过消息在主线程和 worker 之间发送。

worker.postMessage("Hello from main!");
worker.onmessage = function(msg) {
    console.log("Message received from worker thread: " + msg.data);
}

onmessage = function(msg) {
    console.log("Message received from main thread: " + msg.data); // logs: "worker received message: Hello from main!"
    postMessage(" - Hello main! I am worker!");
}


worker 将一直处于活动状态,直到从主线程通过调用 worker.terminate() 显式终止它,或者直到它在 worker 脚本中调用 close() 自行关闭。如果 worker 没有被显式终止或关闭,即使它超出作用域,垃圾回收器也不会收集和处置 worker 实例。

有关 worker API 和用法的更多详细信息,请访问 NativeScript worker 文档

错误处理

除非您在使用 JavaScript 开发时完美无缺,否则 worker 脚本中很可能发生错误。如果后台线程中抛出的错误从未被捕获,则 worker 的 onerror 处理程序将作为最后手段被调用。

onerror = function(e) {
    console.log("Error thrown and not caught: " + e);
    return true;
}


在错误处理程序中返回 true 表示您已处理了异常,您知道自己在做什么,并且不希望它通知主线程有关错误的信息。如果未提供 worker 错误处理程序或它返回 false,则通过调用 worker 实例的错误处理程序通知主线程有关错误的信息。

worker.onerror = function(e) {
    console.log("Error thrown and not caught in worker thread: " + e);
}

我可以将它们用于什么?

虽然我们鼓励您使用新的 worker 功能,但我们想强调的是,worker 并不是解决开发应用程序时可能遇到的所有性能问题的方案。worker 的一个很好的用例是执行 CPU 密集型操作,例如

  • 处理媒体(图像、音频、视频) - 请查看 worker 示例应用程序,它在后台线程上处理图像,同时主线程在没有迟滞的情况下为 UI 对象设置动画。

  • 需要相对频繁执行的繁重计算。例如,在处理大型表格时,更改字段中的值可能会触发一个繁重的操作来重新计算其他字段中的值。worker 不会使繁重操作更快,但它会在后台线程上进行计算时保持 UI 线程的响应。

  • 将阻塞式 API 转换为异步 API。例如,SQLite 库仅公开阻塞式函数,而 NativeScript 插件的一个很好的示例是将 SQLite 库包装在异步 API 中,该 API 在专用 worker 线程上调用阻塞式函数。

  • 查看 社区讨论以获取更多想法!

请记住,如果将 worker 用于不需要花费太多时间的较小任务,则 worker 的初始化和消息传递基础设施可能会增加不必要的开销。为快速的一次性计算初始化一个全新的线程和虚拟机不仅不会提高应用程序的性能,而且肯定会对其产生负面影响。有时很难猜测是否应该在 worker 线程中执行给定的操作。根据经验,如果您已经证明在主线程上执行给定任务会损害 UI 的响应能力,则回退到 worker。

我们遇到了哪些技术问题

  • 尝试限制从 worker 线程访问 UI 元素 - 在原生移动开发中,如果您尝试在后台线程上访问与 UI 相关的 API,您的应用程序很可能会崩溃,并提示您不要触摸 UI。在 Web 中,使用 worker 开发时,您无法访问 document 对象,并且 window 也非常有限,以避免修改 DOM。不幸的是,在 NativeScript 中,我们无法轻松地从非 UI API 中过滤 UI 以公开到 worker 脚本的全局作用域。这意味着您仍然可以完全访问原生 API,但需要注意使用什么以及如何使用它,否则您的应用程序可能会崩溃。

  • 终止挂起的 worker 线程 - 根据 W3C 规范,当在 worker 实例上调用 terminate() 时,应该中断脚本的执行并关闭线程。这允许您终止即使是在其主脚本中具有 while(true) {} 循环的 worker。由于从另一个线程中断和中止脚本是一个非常棘手且容易出错的过程,因此我们正在等待下一个事件循环来执行终止逻辑。这意味着您应该注意不要运行永不结束的任务,因为这会使 worker 无法关闭。

接下来是什么?如何参与?

由于 NativeScript 是一个社区驱动的项目,您可以查看 Android 问题iOS 问题 以查看 worker 的路线图,并分享您的反馈和建议,并通过留下评论参与讨论。想到了一个非常酷的 worker 演示应用程序?创建一个,不要犹豫向社区展示它!