Web Worker:多线程运行JavaScript

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。此外,他们可以使用XMLHttpRequest执行 I/O (尽管responseXML和channel属性总是为空)。一旦创建, 一个worker 可以将消息发送到创建它的JavaScript代码, 通过将消息发布到该代码指定的事件处理程序(反之亦然)。本文提供了有关使用Web Worker的详细介绍。

JavaScript
发布于 2018-05-06 阅读 1.1k

众所周知,JavaScript 是单线程运行的。也就是说,所有任务只能在一个线程上完成,前面的任务没做完,后面的任务只能等着。

那怎么才能避免单线程遇到的一些问题呢?传统的方法是:我们会通过 setTimeout()、setInterval()、ajax 和事件处理程序等技术模拟异步,但只是解决了一部分 JavaScript 的单线程限制。那有没有更好的方法呢?答案是有的,请往下看...

如何多线程运行JavaScript?

Web Workers 是浏览器内置的线程所以可以被用来执行非阻塞事件循环的 JavaScript 代码; 是 HTML5 标准的一部分,这一规范定义了一套 API,为Web内容在后台线程中运行脚本提供了一种简单的方法;它允许一段 JavaScript 程序运行在主线程之外的另外一个线程中,可以执行任务而不干扰用户界面。

正因为如此,在浏览器环境下处理 CPU 计算密集型任务的而不会阻塞、卡顿已成为可能。一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)不会被阻塞或拖慢,从而让程序运行流畅。

值得注意的是,规范中有三种类型的 Web Workers:

  • Dedicated Workers
  • Shared Workers
  • Service workers

本文会依次介绍这三种类型的使用方式!

Dedicated Workers

Dedicated Workers仅仅能被生成它的脚本所使用,下面就创建一个新的worker很简单。需要做的是调用Worker() 的构造器,指定一个脚本的URI来执行worker线程。

var myWorker = new Worker('worker.js');

消息的接收和发送

workers通过postMessage() 方法和onmessage事件处理函数进行消息的接受和发送。向一个worker发送消息需要这样做:

myWorker.postMessage('主线程发送消息到worker:Hello World!');

在worker中接收到消息后,可以写这样一个事件处理函数代码作为响应:

onmessage = function(e) {
  console.log('Message received from main script');
  var workerResult = '收到消息: ' + e.data;
}

onmessage处理函数允许我们在任何时刻,一旦接收到消息就可以执行一些代码,代码中消息本身作为事件的data属性进行使用。还可以使用postMessage()方法,将结果回传给主线程。

postMessage(workerResult);

回到主线程,我们再次使用onmessage以响应worker回传的消息

myWorker.onmessage = function(e) {
  console.log('收到worker发送到消息' + e.data);
}

注意: 当一个消息在主线程和worker之间传递时,它被复制或者转移了,而不是共享 在主线程中使用时,onmessage和postMessage() 必须挂在worker对象上,而在worker中使用时不用这样做。原因是,在worker内部,worker是有效的全局作用域。

终止worker

如果你需要从主线程中立刻终止一个运行中的worker,可以调用worker的terminate 方法

myWorker.terminate();

worker 线程会被立即杀死,不会有任何机会让它完成自己的操作或清理工作。

而在worker线程中,workers 也可以调用自己的 close 方法进行关闭:

close();

worker引入脚本与库

Worker 线程能够访问一个全局函数importScripts()来引入脚本,该函数接受0个或者多个URI作为参数来引入资源;

importScripts('script1.js', 'script2.js');

错误处理

当 worker 出现运行中错误时,它的 onerror 事件处理函数会被调用。它会收到一个扩展了 ErrorEvent 接口的名为 error的事件。 该事件不会冒泡并且可以被取消;为了防止触发默认动作,worker 可以调用错误事件的 preventDefault()方法。

worker.onerror(function (e) {
  console.log(e);
});

Worker 内部也可以监听error事件。

场景案例

待补充

Shared Workers

一个共享worker可以被多个脚本使用——即使这些脚本正在被不同的window、iframe或者worker访问。生成一个新的共享worker与生成一个专用worker非常相似,只是构造器的名字不同,例如:

var myWorker = new SharedWorker('worker.js');

一个非常大的区别在于,与一个共享worker通信必须通过端口对象——一个确切的打开的端口供脚本与worker通信(在专用worker中这一部分是隐式进行的)

在传递消息之前,端口连接必须被显式的打开,打开方式是使用onmessage事件处理函数或者start()方法。

myWorker.port.start();  // 父级线程中的调用
port.start(); // worker线程中的调用, 假设port变量代表一个端口

消息的接收和发送

消息可以像之前那样发送到worker了,但是postMessage() 方法必须被端口对象调用

myWorker.port.postMessage('hello world');

回到worker中,这里也有一些些复杂

onconnect = function(e) {
  var port = e.ports[0];

  port.onmessage = function(e) {
    var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
    port.postMessage(workerResult);
  }
}

首先,当一个端口连接被创建时(例如:在父级线程中,设置onmessage事件处理函数,或者显式调用start()方法时),使用onconnect事件处理函数来执行代码。

使用事件的ports属性来获取端口并存储在变量中。

然后,为端口添加一个消息处理函数用来做运算并回传结果给主线程。在worker线程中设置此消息处理函数也会隐式的打开与主线程的端口连接,因此这里跟前文一样,对port.start()的调用也是不必要的。

最后,回到主脚本,我们处理消息(你会又一次看到 multiply.js 和 square.js中相似的结构):

myWorker.port.onmessage = function(e) {
  result2.textContent = e.data;
  console.log('Message received from worker');
}

当一条消息通过端口回到worker,我们检查结果的类型,然后将运算结果放入结果段落中合适的地方。

场景案例

待补充

  • 本文类型: 原创
  • 本文出处:
  • 版权说明: 本站内容均采用©BY-NC-SA许可协议,版权归作者和本站所有!欢迎转载,但未经作者同意必须在文章页面注明原文出处,否则保留追究法律责任的权利。

坤尘记

I AM KUNCHEN!思绪如风,总会在某处停留;用轻灵的文字书写关于技术生活的奇思妙想。

浙ICP备2020045526号