Javascript Workers - 为什么工作人员消息最近被处理,我可以做些什么吗? [英] Javascript Workers - why is the worker message treated so lately and can I do something against it?

查看:13
本文介绍了Javascript Workers - 为什么工作人员消息最近被处理,我可以做些什么吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个与主线程"共享 SharedArrayBuffer 的 Worker.为了正常工作,我必须确保在主线程访问 SAB 之前工作人员可以访问它.(创建工作线程的代码必须在一个单独的函数中(编辑 2:它返回一个指向 SAB 的数组.)(也许,这已经不可能了,你会告诉我的).

I have a Worker that shares a SharedArrayBuffer with the "main thread". To work correctly, I have to make sure that the worker has access to the SAB before the main thread accesses to it. ( The code creating the worker has to be in a seperate function ( which returns an array pointing to the SAB).) (Maybe, already this is not possible, you'll tell me).

初始代码如下所示:

function init() {
  var code = `onmessage = function(event) {
      console.log('starting');
      var buffer=event.data;
      var arr = new Uint32Array(buffer);// I need to have this done before accessing the buffer again from the main
      //some other code, manipulating the array
  }`
  var buffer = new SharedArrayBuffer(BUFFER_ELEMENT_SIZE);
  var blob = new Blob([code], { "type": 'application/javascript' });
  var url = window.URL || window.webkitURL;
  var blobUrl = url.createObjectURL(blob);
  var counter = new Worker(blobUrl);
  counter.postMessage(buffer);
  let res = new Uint32Array(buffer);
  return res;
}

function test (){
  let array = init();
  console.log('main');
  //accessing the SAB again
};

worker代码总是在test()之后执行,控制台总是显示main,然后是starting.

The worker code is always executed after test(), the console shows always main, then starting.

使用超时没有帮助.考虑以下 test 的代码:

Using timeouts does not help. Consider the following code for test:

function test (){
  let array = [];
  console.log('main'); 
  setTimeout(function(){
    array = initSAB();
  },0);
  setTimeout(function(){
    console.log('main');
   //accessing the SAB again
  },0);
  console.log('end');
};

控制台首先显示end,然后是main,然后是starting.

The console shows end first, followed by main, followed by starting.

但是,即使没有超时,将缓冲区分配给 test() 函数之外的全局数组也可以完成这项工作.

However, assigning the buffer to a global array outside the test() function does the job, even without timeouts.

  • 为什么在发送消息(= 收到?)后工作线程没有直接启动.AFAIK,worker 有自己的事件队列,所以他们不应该依赖主堆栈变空?
  • 是否有详细说明工作人员在发送消息后何时开始工作的规范?
  • 在不使用全局变量的情况下,有没有办法确保在再次访问 SAB 之前工作人员已启动?(可以使用忙等待,但我要小心...)可能没有办法,但我想确定一下.

更准确地说:

  • 在完全并行运行的场景中,Worker 将能够消息发布后立即处理.这显然是不是这样.
  • 大多数浏览器 API(Worker 就是这样的 API)使用回调队列来处理对 API 的调用.但如果这适用,消息将是在执行超时回调之前发布/处理.
  • 更进一步:如果我尝试通过从 SAB 读取直到它更改一个值来忙于在 postMessage 之后等待 将阻止无限编程.对我来说,这意味着浏览器可以不 在调用栈为空之前发布消息我知道,这种行为没有记录在案,我无法解释.
  • In a completly parallel running scenario, the Worker would be able to handle the message immediately after it was posted. This is obviously not the case.
  • Most Browser API (and Worker is such an API) use a callback queue to handle calls to the API. But if this applied, the message would be posted/handled before the timeout calbacks were executed.
  • To go even further: If I try busy waiting after postMessage by reading from the SAB until it changes one value will block the program infinitely. For me, it means that the Browser does not posts the message until the call stack is empty As far as I know, this behaviour is not documentated and I cannot explain it.

总结:如果 postMessage 的调用在函数内部,我想知道浏览器如何确定何时发布消息并由工作人员处理它.我已经找到了一个解决方法(全局变量),所以我更感兴趣的是它在幕后是如何工作的.但如果有人可以向我展示一个可行的例子,我会接受.

To summerize: I want to know how the browser determines when to post the message and to handle it by the worker, if the call of postMessage is inside a function. I already found a workaround (global variables), so I'm more interested in how it works behind the scenes. But if someone can show me a working example, I'll take it.

使用全局变量的代码(工作正常的代码)看起来像这样

The code using the global variable (the code that works fine) looks like this

function init() {
//Unchanged
}

var array = init(); //global

function test (){
  console.log('main');
  //accessing the SAB again
};

它将starting,然后main 打印到控制台.

It prints starting, then main to the console.

还有什么值得注意的:如果我用 Firefox 浏览器调试代码(Chrome 未测试)我得到了我想要的结果,没有全局变量(starting before main) 有人能解释一下吗?

What is also worth noticing : If I debug the code with the Firefox Browser (Chrome not tested) I get the result I want without the global variable (starting before main) Can someone explain?

推荐答案

为什么在消息被发送[t](=收到?)后工作人员没有直接启动.AFAIK,worker 有自己的事件队列,所以他们不应该依赖主堆栈变空?

首先,即使您的 Worker 对象在主线程中同步可用,在实际的工作线程中也有 在能够处理您的消息之前需要做很多事情:

First, even though your Worker object is available in main thread synchronously, in the actual worker thread there are a lot of things to do before being able to handle your message:

  • 它必须执行网络请求以检索脚本内容.即使使用 blobURI,它也是一个异步操作.
  • 它必须初始化整个 js 上下文,因此即使网络请求闪电般快速,这也会增加并行执行时间.
  • 它必须在主脚本执行之后等待事件循环帧来处理您的消息.即使初始化速度快如闪电,它也会等待一段时间.
  • it has to perform a network request to retrieve the script content. Even with a blobURI, it's an async operation.
  • it has to initialize the whole js context, so even if the network request was lightning fast, this would add up on parallel execution time.
  • it has to wait the event loop frame following the main script execution to handle your message. Even if the initialization was lightning fast, it will anyway wait some time.

因此,在正常情况下,您的 Worker 在您需要数据时执行您的代码的可能性很小.

So in normal circumstances, there is very little chances that your Worker could execute your code at the time you require the data.

现在你谈到了阻塞主线程.

Now you talked about blocking the main thread.

如果我尝试通过从 SAB 中读取来忙于等待 postMessage 直到它改变一个值将无限阻塞程序

If I try busy waiting after postMessage by reading from the SAB until it changes one value will block the program infinitely

您的 Worker 初始化期间,消息暂时保持在主线程上,在所谓的外部端口中.只有在获取脚本之后,这个 外部端口内部端口纠缠在一起,并且消息实际上传递到该并行线程.
因此,如果您在端口被纠缠之前阻塞了主线程,它将无法将其传递给工作线程.

During the initialization of your Worker, the message are temporarily being kept on the main thread, in what is called the outside port. It's only after the fetching of the script is done that this outside port is entangled with the inside port, and that the messages actually pass to that parallel thread.
So if you do block the main thread before the ports have been entangled it won't be able to pass it to the worker's thread.

是否有详细说明工作人员在发送消息后何时开始工作的规范?

当然,更具体地说,端口消息队列在第 26 步,并且事件循环实际上是在 第 29 步.

Sure, and more specifically, the port message queue is enabled at the step 26, and the Event loop is actually started at the step 29.

在不使用全局变量的情况下,有没有办法在再次访问 SAB 之前确保工作线程已经启动?[...]

当然,让你的 Worker 在它发布时向主线程发布一条消息.

Sure, make your Worker post a message to the main thread when it did.

// some precautions because all browsers still haven't reenabled SharedArrayBuffers
const has_shared_array_buffer = window.SharedArrayBuffer;

function init() {
  // since our worker will do only a single operation
  // we can Promisify it
  // if we were to use it for more than a single task, 
  // we could promisify each task by using a MessagePort
  return new Promise((resolve, reject) => {
    const code = `
    onmessage = function(event) {
      console.log('hi');
      var buffer= event.data;
      var arr = new Uint32Array(buffer);
      arr.fill(255);
      if(self.SharedArrayBuffer) {
        postMessage("done");
      }
      else {
        postMessage(buffer, [buffer]);
      }
    }`
    let buffer = has_shared_array_buffer ? new SharedArrayBuffer(16) : new ArrayBuffer(16);
    const blob = new Blob([code], { "type": 'application/javascript' });
    const blobUrl = URL.createObjectURL(blob);
    const counter = new Worker(blobUrl);
    counter.onmessage = e => {
      if(!has_shared_array_buffer) {
        buffer = e.data;
      }
      const res = new Uint32Array(buffer);
      resolve(res);
    };
    counter.onerror = reject;
    if(has_shared_array_buffer) {
      counter.postMessage(buffer);
    }
    else {
      counter.postMessage(buffer, [buffer]);
    }
  });
};

async function test (){
  let array = await init();
  //accessing the SAB again
  console.log(array);
};
test().catch(console.error);

这篇关于Javascript Workers - 为什么工作人员消息最近被处理,我可以做些什么吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆