-1

I'm trying to postMessage from a web worker and update the UI with the response. Here is my web worker code :

/// <reference lib="webworker" />

addEventListener('message', ({ data }) => {

  let result = 0;
  for(let i = 0; i < data.length; i++){
    if(data[i] == "P1")
    {
      postMessage(i);
    }
  }
  
});

The data is the above code, will be too large let's say 10000000. And most of the time the if condition would be satisfied, so frequent postMessage calls. Here is the Angular code using which I'm updating the UI.

this.worker = new Worker('./app.worker', { type: 'module' });
this.worker.onmessage = ({ data }) => {
   this.result = data;
};

The Angular web app UI hangs when I run this code. I'm trying to update the count in the UI using web worker. Any pointers to why the UI hangs ??

iJade
  • 23,144
  • 56
  • 154
  • 243
  • 1
    Out of curiosity, why `postMessage` one billion times? – mbojko Jul 02 '20 at 19:38
  • Let's say I'm checking for something in an array of length that large. Each a time a condition is satisfied I need to update the UI, that is postMessage. @mbojko – iJade Jul 03 '20 at 05:00
  • If you do it this way (and a lot of entries match the condition) i would guess there is absolutely no reason to use a Worker at all. You could just do it in the main thread with similiar results. Maybe try to process your data in batches and then send it back to the main thread. – tobiso Jul 03 '20 at 06:23
  • You still don't have to try and post one billion messages in the same clock tick. You can first go through the entire collection, and post one message with all the relevant data. – mbojko Jul 03 '20 at 07:10

2 Answers2

0

maybe try this:

addEventListener('message', ({ data }) => {
  postMessage();
});

there's no reason you would want to postMessage 1 billion times.

Rick
  • 1,710
  • 8
  • 17
  • Let's say I'm checking for something in an array of length 1billion. Each time a condition is satisfied I need to update the UI, that is postMessage. The condition is satisfied most of time. – iJade Jul 03 '20 at 05:05
  • postMessage should fire one time. if, in postMessage you have to loop through an array, then do it, but no reason to fire the same method multiple times at once. that concept doesn't even make sense in computing. – Rick Jul 03 '20 at 15:24
0

No surprises here, spamming your event loop with message events is not good.
Every time you postMessage when the main thread is busy it has to keep the message event in a queue, and handling one message will keep the event loop busy, so posting 1 billion message will make the queue grow incredibly and eat all resources.

You have to redesign your code:

What you need is to have the Worker thread sends all the information it could generate thus far at some interval (e.g based on requestAnimationFrame frequency).
This way, your main thread only has to handle one message per rendering frame, and it can render all the data in one call.

The tricky part is to let the Worker thread know when it has to send its data.

For this, the best is to make it work by batches, and after each batch, wait just the next event loop iteration so our interval timer can slip through when required.

Here is a proof of concept rendering an infinite operation, which uses the MessageChannel API as a 0 timeout task scheduler engine used to let a requestAnimationFrame callback fire when needed and performing 10000 operations per task (a lot more per frame if your computer is fast enough). This setup has the advantage of not slowing too much the overall processing that the Worker has to do.

const log = document.getElementById( "log" );
const script = document.querySelector( '[type="worker-script"]' );
const blob = new Blob( [ script.textContent ], { type: "text/javascript" } );
const worker = new Worker( URL.createObjectURL( blob ) );
worker.onmessage = ({ data }) => {
  const [ PI, iterations ] = data;
  log.textContent = `π = ${ PI }
after ${ iterations } iterations.`
};
<script type="worker-script">
// First some helpers 
// 
// Uses a MessageChannel to perform a 0 timeout scheduler
const postTask = (() => {
  const channel = new MessageChannel();
  channel.port1.start();
  return (cb) => {
    channel.port1.addEventListener( 'message', () => cb(), { once: true } );
    channel.port2.postMessage( "" );
  };
})();
// Promise wrapper around 'postTask'
const waitForNextEventLoopIteration = () =>
  new Promise( res => postTask( res ) );
// WorkerScope.rAF is available in Chrome, not in FF
if( !self.requestAnimationFrame ) {
  self.requestAnimationFrame = (cb) => setTimeout( cb, 16 );
}


// The actual processing
// borrowed from https://stackoverflow.com/a/50282537/3702797
// [addition]: made async so it can wait easily for next event loop
async function calculatePI( iterations = 10000 ) {

  let pi = 0;
  let iterator = sequence();
  let i = 0;
  // [addition]: start a new rAF loop
  // which will report to main the current values
  requestAnimationFrame( function reportToMain() {
    postMessage( [ pi, i ] );
    requestAnimationFrame( reportToMain );
  } );

  // [addition]: define a batch_size
  const batch_size = 10000;

  for( ; i < iterations; i++ ){
    pi += 4 /  iterator.next().value;
    pi -= 4 / iterator.next().value;
    // [addition]: In case we made one batch,
    // we'll wait the next event loop iteration
    // to let the rAF callback fire.
    if( i % batch_size === 0 ) {
      await waitForNextEventLoopIteration();
    }
  }

  function* sequence() {
    let i = 1;
    while( true ){
      yield i;
      i += 2;
    }
  }
}

// Start the *big* job...
calculatePI( Infinity );
</script>
<pre id="log"></pre>
Kaiido
  • 123,334
  • 13
  • 219
  • 285