高级概念
多线程和工作线程
在主线程上执行繁重的任务是不允许的,幸运的是 NativeScript 可以使用多线程!
NativeScript 的优势之一是它允许通过 JavaScript 快速有效地访问所有原生平台 (Android/Objective-C) API,无需使用(反)序列化或反射。JavaScript 在主线程(也称为 UI 线程)上执行。这意味着可能需要较长时间的操作可能会延迟 UI 的渲染并导致应用程序出现延迟。
为了解决 UI 清晰度和高性能至关重要的缓慢问题,开发人员可以使用工作线程。工作线程是在绝对隔离的上下文中后台线程上执行的脚本。应将可能需要很长时间才能执行的任务卸载到工作线程。
NativeScript 中的 Worker API 松散地基于Web Worker API和Web Worker 规范。
为了在使用 Worker API 时获得最佳效果,请遵循以下准则
- 始终确保在工作线程完成其工作后使用相应的 API (
terminate()
或close()
) 关闭工作线程。如果 Worker 实例在您能够终止它之前在您正在使用的范围内变得无法访问,则您只能从工作线程脚本本身内部通过调用close()
函数来关闭它。 - Worker 不是所有性能相关问题的通用解决方案。启动 Worker 本身会产生开销,有时可能比处理快速任务慢。在求助于工作线程之前,请优化数据库查询或重新考虑复杂的应用程序逻辑。
- 由于工作线程可以访问整个原生 SDK,因此 NativeScript 开发人员必须在调用并非保证从多个线程安全访问的 API 时注意所有同步问题。
使用工作线程
生成工作线程
创建新的工作线程很简单,您只需要使用工作线程脚本的路径(相对于当前文件,或别名路径,例如 ~/path/to/worker.ts
)调用 Worker()
构造函数即可。工作线程本身可以用 JavaScript 或 TypeScript 编写。
const myWorker = new Worker('./worker.ts')
向工作线程发送消息
要向工作线程发送消息,请使用 postMessage()
方法在 Worker
实例上调用。
// send myMessage to the worker
myWorker.postMessage(myMessage)
接收来自工作线程的消息
要接收来自工作线程的消息,请使用 onmessage
回调在 Worker
实例上调用。
// attach a message handler that will receive messages from the worker thread
myWorker.onmessage = (e) => {
console.log('Message received from the worker thread.')
const data = e.data // data from the worker
}
向主线程发送消息
要将消息发送回主线程,请在工作线程中使用 self.postMessage()
方法。
// send the workerResult back to the main thread
self.postMessage(workerResult)
接收来自主线程的消息
要在工作线程中接收消息,请在工作线程中使用 self.onmessage
事件处理程序。
// attach a message handler that will receive the messages from the main thread
self.onmessage = (e) => {
console.log('Message received from the main thread.')
const data = e.data // data from myMessage
}
终止工作线程
如果您需要停止执行工作线程,您可以使用 Worker
实例上的 terminate
方法从主线程终止它
myWorker.terminate()
工作线程将立即被终止。
处理错误
工作线程内部的运行时错误将通过 Worker
实例上的 onerror
回调报告回主线程。
myWorker.onerror = (e) => {
console.log(
`Error occured in the worker thread in file ${e.filename} on line ${e.lineno}`
)
console.log(e.message, e.stackTrace)
}
工作线程还可以通过设置 onerror
处理程序来自行处理错误,该处理程序可以将错误标记为“已处理”并阻止调用主线程上的回调。
self.onerror = (e) => {
console.log('Error occured, error:', e)
// return true-like to stop the event from being passed onto the main thread
return true
}
Worker API
构造函数
const myWorker = new Worker(path: string)
创建 Worker 的实例并生成一个新的操作系统线程,在该线程上执行 path
参数指向的脚本。
postMessage
myWorker.postMessage(message)
将可 JSON 序列化的消息发送到关联脚本的 onmessage
事件处理程序。
terminate
myWorker.terminate()
在下一个运行循环滴答时终止工作线程的执行。
onmessage
myWorker.onmessage = function handler(message: { data: any }) {}
处理来自关联工作线程的传入消息。注意您负责创建和设置处理程序函数。
message
对象中的 data
是工作线程使用 postMessage 发送的可 JSON 序列化的消息。
onerror
myWorker.onerror = function handler(error: {
message: string // the error message
stackTrace?: string // the stack trace if applicable
filename: string // the file where the uncaught error was thrown
lineno: number // the line where the uncaught error was thrown
}) {}
处理来自工作线程的未捕获错误。注意您负责创建和设置处理程序函数。
Worker 全局作用域
每个工作线程都有自己的全局作用域(因此工作线程中的 global.foo
与主线程上的 global.foo
不相同)。
self
self === global // true
返回对 WorkerGlobalScope
本身的引用 - 也可作为 global
使用
postMessage
self.postMessage(message)
将可 JSON 序列化的消息发送到主线程上 Worker 实例的 onmessage
事件处理程序。
close
self.close()
在下一个运行循环滴答时终止工作线程的执行。
onmessage
self.onmessage = function handler(message: { data: any }) {}
处理来自主线程的传入消息。
message
对象中的 data
是主线程使用 postMessage 发送的可 JSON 序列化的消息。
onerror
self.onerror = function handler(error: Error): boolean {
return true
}
处理在 Worker 作用域(工作线程)内执行函数期间发生的未捕获错误。
如果处理程序返回 true
类值,则消息将不会传播到主线程上 Worker 实例的 onerror
处理程序。
在工作线程中调用 onerror
后,执行不会终止,并且工作线程仍然能够发送/接收消息。
onclose
self.onclose = function handler() {
// do cleanup work
}
处理任何“清理”工作。适用于释放资源、关闭流和套接字。
工作线程示例
注意
为了使用 setTimeout
、setInterval
或来自 @nativescript/core
的其他全局变量,您需要将它们包含在工作线程脚本中
import '@nativescript/core/globals'
// main-view-model.js
const worker = new Worker('./workers/image-processor')
// send a message to our worker
worker.postMessage({ src: imageSource, mode: 'scale', options: options })
// handle incoming messages from the worker
worker.onmessage = function (message) {
if (message.data.success) {
// the src received from the worker
const src = message.data.src
// terminate worker or send another message...
worker.terminate()
} else {
// handle unsuccessful task
}
}
// handle worker errors
worker.onerror = function (err) {
console.log(
`An unhandled error occurred in worker: ${err.filename}, line: ${err.lineno} :`,
err.message
)
}
// workers/image-processor.js
// load NativeScript globals in the worker thread
import '@nativescript/core/globals'
self.onmessage = function (message) {
const src = message.data.src
const mode = message.data.mode || 'noop'
const options = message.data.options
const result = processImage(src, mode, options)
if (result) {
// send the result back to the main thread
self.postMessage({
success: true,
src: result,
})
return
}
// no result, send back an empty object for example
self.postMessage({})
}
// example heavy function to process an image
function processImage(src, mode, options) {
console.log(options)
// image processing logic
// save image, retrieve location
// return source to processed image
return updatedImgSrc
}
限制
在使用工作线程时,需要牢记某些限制
- 没有 JavaScript 内存共享。这意味着您无法从两个线程访问 JavaScript 值/对象。您只能序列化对象,将其发送到另一个线程并在那里反序列化它。这就是
postMessage()
函数负责的功能。但是,对于原生对象并非如此。您可以从多个线程访问原生对象,而无需复制它,因为运行时将为每个线程创建一个单独的 JavaScript 包装器对象。请记住,当您使用非线程安全的原生 API 和数据时,您必须自行处理同步部分。运行时不会对原生数据访问和 API 调用执行任何锁定或同步逻辑。 - 只能使用
postMessage()
API 发送可 JSON 序列化的对象.- 您不能发送原生对象。这意味着您不能使用 postMessage 发送原生对象,因为在大多数情况下,原生对象的 JavaScript 包装器的 JSON 序列化会导致空对象文字 - "{}"。另一方面,此消息将被反序列化为纯空 JavaScript 对象。发送原生对象是我们将来想要支持的功能。
- 发送循环对象时要小心,因为它们的递归节点将在序列化步骤中被剥离。
- 没有对象传输。如果您是 Web 开发人员,您可能熟悉浏览器中的 ArrayBuffer 和 MessagePort 传输支持。目前,在 NativeScript 中,没有像对象传输这样的概念。
- 目前无法调试工作线程。
- 不支持嵌套工作线程。我们希望了解社区是否需要我们支持此功能。