Interrupting a looping WebWorker [duplicate]
Asked Answered
F

1

6

I have a Dedicated Webworker that upon receiving a starting signal goes into a long loop and based on some startup settings the loop would "yield" at given points of execution.

This is a simplified version of my code

var mode = null;
var generator = null;

function* loop() {

    for(var i=0;i<10000;i++) {
        //Do stuff
        for(var j=0;j<10000;j++) {
            //Do stuff
            if( mode == 'inner' ){
                //Yield after each inner loop iteration
                yield 2;
            }
        }
        if( mode == 'outer' ){
            //Yield after each outer loop iteration
            yield 1;
        }
    }

    /*
    If mode is not inner or outer the function won't yield 
    and will process the whole loop in one shot
    */
    return null;

}

generator = loop();

self.onmessage = function(event) {

    var m = event.data;    
    if(m.operation == 'run') {
        mode = m.mode;
        generator.next();
    }

    if(m.operation == 'pause') {
        //Set a flag and check for it in the loop
    }
}

What I want to do is to allow a worker to be paused on demand, the problem is that while in the loop the worker won't process messages and onmessage won't be called so I cannot send a "pause" message that sets a flag and then I check that flag in the loop.

What I thought of doing is to have my function yield after each iteration to allow the worker thread to process the message queue and then resume the function again if no pause signals is received, however, this feels a bit hacky.

Is there a way to force the WebWorker to process message queue without leaving the loop or yielding? or maybe a way to set a flag without going through onmessage()?

Thank you

Fun answered 20/1, 2016 at 12:57 Comment(3)
No, you have to do one of these - leave the loop or yield - unless something really basic has changed in the core of JS event loop. Javascript has no multithreading within one context so there's no way both your loop and onmessage were executed simultaneously. If your platform supports yield I advise using it rather than terminating the loop.Bema
I was thinking maybe of a function similar to DoEvents in C# that would allow me to let Javascript process the queue, this won't require multithreadingFun
No, you can't call the queue from your code. Actually if you think about it, that would be a recursion - calling event loop from event loop.Bema
E
0

Is there a way to force the WebWorker to process message queue without leaving the loop or yielding? or maybe a way to set a flag without going through onmessage()?

Nope. Worker is single-threaded, just like the main thread. Only one line can be executed at a time and there are fortunately no gotos.

Also your solution has a flaw that it will not use CPU all the time. After the loop yields, it will not continue until you call .next in the message event listener. I propose async/await alternative:

function Timeout(time) {
    return new Promise(resolve=>setTimeout(resolve, time));
}
let mode = "inner";
async function loop() {
    for(var i=0;i<10000;i++) {
        //Do stuff
        for(var j=0;j<10000;j++) {
            //Do stuff
            if( mode == 'inner' ){
                await Timeout(0);
            }
        }
        if( mode == 'outer' ){
            await Timeout(0);
        }
    }
}
// or you can start it only after you receive the right message
const workerLoop = loop();
(async () {
  await workerLoop;
  self.postMessage("done");
})();

Regarding pausing, you can also use a promise:

let mode = "inner";
let pausePromise = null;
async function loop() {
    for(var i=0;i<1000;i++) {
        //Do stuff
        for(var j=0;j<1000;j++) {
            if(pausePromise) {
              console.log("Loop paused");
              await pausePromise;
              console.log("Loop resumed");
            }
        }
    }
}
let workerLoop = null;
self.onmessage = function(event) {
    var m = event.data;    
    if(m.operation == 'run') {
        mode = m.mode;
        if(!workerLoop) {
          workerLoop = loop();
        }
    }

    if(m.operation == 'pause') {
        if(workerLoop) {
          var listener = null;
          pausePromise = new Promise(resolve=>self.addEventListener("message", listener = (event)=>{
              if(event.data.operation=="run") {
                console.log("Resuming loop from promise.");
                self.removeEventListener("message", listener);
                pausePromise = null;
                resolve();
              }
          }))
        }
        else {
          console.warn("Not running!");
        }
    }
}

This is a bit dirty but it works. Here is it working in a JS fiddle: https://jsfiddle.net/Darker/pvy6fszL/16/

Electrostatics answered 2/8, 2019 at 13:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.