简体   繁体   中英

Synchronously Wait for Message in Web-Worker

Is there some way to synchronously wait for or check for a new message in a web-worker?

I have a large complicated body of code (compiled LLVM from emscripten) that I cannot refactor around callbacks.

I need to make sure that code after a certain line doesn't execute until I receive and handle a message from the UI-thread. If I block with a while-loop, the event-loop never runs so I can't receive messages.

This is an issue that I also ran into while working with Pyodide . I wanted to 'synchronously' call a function from the main thread.

One solution involves the Atomic s and SharedArrayBuffer APIs. From the perspective of the web worker, this looks like the following

  1. postMessage the main thread
  2. freeze ourselves with Atomics.wait
  3. get unfrozen by the main thread
  4. read the result from a SharedArrayBuffer . We can't receive the result as a postMessage , because there isn't a synchronous way of asking "did I get a message".

Of course this requires a fair amount of extra code to handle all the serialization, data passing and more.

The main limitation is that to use those APIs, one needs to have the COOP/COEP headers set . The other bit to keep in mind is that this only works on recent browsers such as Safari 15.2 (released in December 2021).

There is also an alternative solution with synchronous XHR and a service worker , but I haven't investigated that option.

A crude hack to do this could be to use a synchronous API, such as the FileSystem API, and use a temporary file as a synchronising mechanism.

In your example, the emscripten code could issue a synchronous read on a file, while the UI thread writes to the same file, effectively unblocking the worker.

No, unfortunately. There was some discussion of adding a way to block on a message from the parent page, but it went nowhere. I'll see if I can dig up the relevant mailing list thread.

Can you break the sequence as follow without refactoring?

wrk.onmessage = function(e) {
    if (e.data.step === 'initial input') {
        doInitialWork(e.data.inputs)
    } else if (e.data.step === 'ui input') {
        doTheRest(e.data.uiData)
    }
}

The onmessage event will be blocked until the initial step execution stack is complete and the critical ui bits will only launch when and if the info is available.

This is how I've done this. It's really sorrowful to think that Web Workers haven't received the await message functionality, which would make the whole inter-thread communication concept a lot closer to Go language for example. But no, they ain't did it, so have to invent stuff in order to keep threads synchronised

  let resolver: any = undefined;
  let message: any = undefined;
  const messagePromise = new Promise((resolve) => resolver = resolve);

  worker.onmessage = async ({ data: payload }: { data: any }) => {
    message = payload;
    worker.onmessage = undefined;
    resolver?.();
  }

  await messagePromise;

Now, this is Typescript. Just remove the types to get Javascript. Note that there is no way to capture a message if it had been sent before we started listening for messages

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM