The JavaScript Event Loop and Web Workers - javascript

So I've been having a long talk with a colleague regarding the JavaScript event loop and the use of Web Workers. In a single Web page, different Web Workers have different stacks, heaps, and message queues, form here, specifically:
A web worker or a cross-origin iframe has its own stack, heap, and message
queue. Two distinct runtimes can only communicate through sending messages via
the postMessage method. This method adds a message to the other runtime if the
latter listens to message events.
but are all the messages executed inside the same event loop, or does each Web Worker have its own event loop?
I'm asking this because I have two Web Workers in a page, one executes a very computationally-heavy operation in sequence, while the other just handles a WebRTC connection.
I will not go into details but it seems to me that the computationally-heavy Web Worker is taking away so much computational time out of the JavaScript event loop that the other Worker, that only has to keep the connection alive (through heartbeat I suppose) isn't able to do so, and the connection is eventually lost.
This is what I believe. If that is not the case, and the two Web Workers work on different event loops then I cannot explain why the connection is lost when the load on the computing Web Worker is heavy (when the load is light then the connection is not lost).

Each worker has its own event loop. From the specification:
Each WorkerGlobalScope object has a distinct event loop, separate from those used by units of related similar-origin browsing contexts.
and then here:
The global scope is the "inside" of a worker.
...which is followed by the definition of the WorkerGlobalScope interface referenced in the earlier quote.
Your computation-heavy worker might be dominating the available processing time, but it isn't blocking the other worker's event loop.
We can also readily check this with a quick test:
page.html:
<!DOCTYPE HTML "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta charset="UTF-8">
<title>Two Workers</title>
<style>
body {
font-family: sans-serif;
}
pre {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div>Fast: <span id="fast"></span></div>
<div>Slow: <span id="slow"></span></div>
<script>
(function() {
var fastWorker = new Worker("fastworker.js");
var fast = document.getElementById("fast");
var slowWorker = new Worker("slowworker.js");
var slow = document.getElementById("slow");
fastWorker.addEventListener("message", function(e) {
fast.innerHTML = e.data || "??";
fastWorker.postMessage("ping");
});
slowWorker.addEventListener("message", function(e) {
slow.innerHTML = e.data || "??";
slowWorker.postMessage("ping");
});
fastWorker.postMessage("start");
slowWorker.postMessage("start");
})();
</script>
</body>
</html>
slowworker.js:
var counter = 0;
self.addEventListener("message", function(e) {
var done = Date.now() + 1000; // 1 second
while (Date.now() < done) {
// Busy wait (boo!)
}
++counter;
self.postMessage(counter);
});
fastworker.js:
var counter = 0;
self.addEventListener("message", function(e) {
var done = Date.now() + 100; // 100ms
while (Date.now() < done) {
// Busy wait (boo!)
}
++counter;
self.postMessage(counter);
});
As you can see, "fast"'s number goes up much more quickly than "slow", showing it's processing its messages.
(I could have made one worker file and sent the delay in the start command, but...)

Related

Does a "message" event handler process messages in a serial or concurrent fashion?

If some page A sends a sequence of messages to page B that listens for and handles them, does the event listener process those messages in a serial fashion? If it does so in concurrent fashion, I have to be careful about data races, don't I?
In particular, consider some page A (at https://a.example.com/index.html):
<script>
const win = open('https://b.example.com/index.html');
setTimeout(() => {
for (let i = 0; i < 1024; i++) {
win.postMessage(i, '*');
}
}, 1000);
</script>
and page B (at https://b.example.com/index.html):
<script>
let count = 0;
window.addEventListener('message', () => {count++});
</script>
When all messages have been processed, is page B's count variable guaranteed to have value 1024? Or is there no such guarantee, because messages are being processed in a concurrent manner and updates to the count variable may be lost?
The Web Messaging standard is not the easiest to read... It does mention queues, which seem to indicate serial (not concurrent) handling of messages, but I'd like to be sure.
Each MessagePort has its own queue (task source) where all the messages are queued. Then when the event loop will choose this queue, an Event will be dispatched from the queued task. The event loop will always pick the oldest task from a queue (FIFO order). Well, to be 100% technically correct, it will choose the first runnable task of the task queue, but in this particular case that's exactly the same.
If A is the only source of a new message in B, then yes, you can be sure that the value will be 1024.

How to play audio TTS in queue?

I'm trying to make speech for twitch chat messages through Azure TTS. In this case, everything works, but the messages are played at the same time. How can I make messages play in sequence?
<html>
<head>
<script src="comfy.min.js"></script>
</head>
<body>
<script src="microsoft.cognitiveservices.speech.sdk.bundle.js"></script>
<script type="text/javascript">
function synthesizeSpeech(message) {
var speechConfig = SpeechSDK.SpeechConfig.fromSubscription("AzureKey", "AzureRegion");
speechConfig.speechSynthesisVoiceName = "en-US-Zira";
speechConfig.SpeechSynthesisLanguage = "en-US";
var synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig);
synthesizer.speakTextAsync(message); // twitch message speech
};
ComfyJS.onChat = (user, message, flags, self, extra) => {
if( flags.broadcaster === true ) {
console.log(message); //display message
synthesizeSpeech(message); // start function speech
}
}
ComfyJS.Init( "TwitchChannel" );
</script>
</body>
</html>
I believe the issue here is that the ComfyJS.onChat/synthesizeSpeech() function is getting called multiple times on different threads, or at least multiple times without waiting for the previous speakTextAsync call to finish speaking.
I would experiment with making "var synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig)" globally scoped variable, so that you are using a single synthesizer to speak all the incoming messages, rather than a new synthesizer for each message. using a single tts engine should cause them to queue up and render in order.
Alternatively you could wait for speakTextAsync() to finish before allowing another synthesizer and message to be created and queued, but I think it would be more efficient to use a single synthesizer instance for the entire chat/conversation.
Brian.

Javascript: Alternative to setTimeOut for FAST Timer in MIDI Sequencer App

I'm working on a Javascript Music App that includes a Sequencer. For those who are not familiar, MIDI sequencers work pretty much like this: There is something called PPQ: pulses per quarter note. Each pulse is called "Tick". It depicts how may "subdivisions" there are per quarter note, like resolution. So Sequencers "play" the Events that are in the tracks one Tick at a time: Play Tick1, wait Tick Duration, Play tick2, Tick Duration, and so on.
Now, let's say we have a BPM (Beats per Min) of 120 with PPQ=96 (standard). That means that each Quarter Note Duration is 500ms, and each Tick Duration is 5.20833ms.
What Timer Alternatives we have in Javascript?
1) We have the old setTimeOut. It has several problems: the min. wait time is 4ms. (https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Minimum_delay_and_timeout_nesting)
It is also subject to JITTER/time Variations. It is not precise and it is demanding, as call backs are stacked in the even loop.
2) There is an alternative to setTimeOut/setInterval which involves using requestAnimationFrame(). It is VERY precise and CPU efficient. However, the minimum time it can be set is around 16.7ms (the duration of a Frame in a typical 60FPS monitor)
Is there any other Alternative? To to precisely schedule an event every 2-5ms?
Note: the function done in side the loop, playEventsAtTick() is NOT demanding at all, so it would never take more time to execute than Tick Duration.
Thanks!
Danny Bullo
To maintain any sanity in doing this kind of thing, you're going to want to do the audio processing on a devoted thread. Better yet, use the Web Audio API and let people who have been thinking about these problems for a long time do the hard work of sample-accuracy.
Also check out Web MIDI (chrome only).
Thanks nvioli. I'm aware of Web Audio API. However, I don't think that can help here.
I'm not triggering AUDIO directly: I have MIDI events (or let's say just "EVENTS") stored in the TRACKS. And those events happen at any TICK. So the Sequencer needs to loop every Tick Duration to scan what to play at that particular tick.
Regards,
Danny Bullo
In a separate thread, such as a web worker, you can create an endless loop. In this loop, all you need to do is calculate the time between beats. After the time is valid, you can then send a message to the main process, to do some visuals, play a sound or what ever you would like to do.
Here is a Working example
class MyWorker {
constructor() {
// Keeps the loop running
this.run = true
// Beats per minute
this.bpm = 120
// Time last beat was called
this.lastLoopTime = this.milliseconds
}
get milliseconds() {
return new Date().getTime()
}
start() {
while (this.run) {
// Get the current time
let now = this.milliseconds
// Get the elapsed time between now and the last beat
let updateLength = now - this.lastLoopTime
// If not enough time has passed restart from the beginning of the loop
if (updateLength < (1000 * 60) / this.bpm) continue;
// Enough time has passed update the last time
this.lastLoopTime = now
// Do any processing that you would like here
// Send a message back to the main thread
postMessage({ msg: 'beat', time: now })
}
}
}
new MyWorker().start()
Next we can create the index page, which will run the worker, and flash a square everytime a message comes back from the worker.
<!DOCTYPE html>
<html lang="en">
<head>
<script>
// Start the worker
var myWorker = new Worker('worker.js')
// Listen for messages from the worker
myWorker.onmessage = function (e) {
var msg = e.data
switch (msg.msg) {
// If the message is a `beat` message, flash the square
case 'beat':
let div = document.querySelector('div')
div.classList.add('red')
setTimeout(() => div.classList.remove('red'), 100)
break;
}
}
</script>
<style>
div { width: 100px; height: 100px; border: solid 1px; }
.red { background: red; }
</style>
</head>
<body>
<div></div>
</body>
</html>
Get Off My Lawn: The approach you suggested does not completely work. Let's say I add a method to the web worker to STOP the Sequencer:
stop() {
this.run = false;
}
The problem is that the method myWorker.onmessage = function (e) {...} never get's triggered. I suspect it is because the Web Worker Thread is "TOO BUSY" with the endless loop. any way to solve that?
Also, while playing, it works.....but the CPU goes up considerably..... The only possible Solution would be a Sleep() method, but Real SLEEP that does not exist in Javascript...
Thanks

Javascript does not print text immediately

I wrote this code which is supposed to say "hi" when I click the "hello" button:
<!DOCTYPE html>
<html>
<head>
<script>
var someLargeNumber = 5000000000;
function hello() {
document.getElementById('hi').innerHTML = "hi";
for(var i = 0; i < someLargeNumber; i++) {}
}
</script>
</head>
<body>
<p id="hi"></p>
<input type="button" value="hello" onclick="hello();">
</body>
</html>
It does say hi, but only after the for loop is finished. Why does this happen and how do I fix this?
Thanks
Why does this happen...
Because browsers run JavaScript on the main UI thread they use for updating the page, for a variety of reasons. So although you've shown the "hi" text, it doesn't get rendered until the JavaScript code running in response to the event completes.
...and how do I fix this?
Yield back to the browser after adding the text, before doing whatever it is that you're simulating with that loop. setTimeout with a delay of 0 is suitable for many cases:
var someLargeNumber = 5000000000;
function hello() {
document.getElementById('hi').innerHTML = "hi";
setTimeout(function() {
for(var i = 0; i < someLargeNumber; i++) {}
}, 0);
}
The JavaScript engine works basically in a loop with a task queue (the spec calls them "jobs"). It picks up a job from the queue, runs it to completion, and then looks for the next job. Browsers (usually) update the UI when the engine is between jobs. When an event occurs, a job is queued to call the event handler. The above just moves the loop into a new job it queues via setTimeout, so the browser has a chance after the event job and before the setTimeout job to update the UI.
As already answered browser has single UI thread.
Another option is to use Web Worker (provided you are not doing any DOM manipulations in the worker thread), which allows to run operations in an another thread.
Add another js file (say worker.js)
var someLargeNumber = 5000000000;
onmessage = function(e) {
console.log('Message received from main script');
for(var i = 0; i < someLargeNumber; i++) {}
console.log('Posting message back to main script');
postMessage('done');
}
Back in you main file
<head>
<script>
var myWorker = new Worker("worker.js");
function hello() {
document.getElementById('hi').innerHTML = "hi";
myWorker.postMessage('test');
console.log('Message posted to worker');
}
myWorker.onmessage = function(e) {
result.textContent = e.data;
console.log('Worker thread is complete');
}
</script>

Background tasks in JavaScript that do not interfere UI interaction

I am working on a mobile App implemented in JavaScript that has to do lots of background stuff -- mainly fetching data from a server (via JSONP) and writing it into a database using local storage. In the foreground the user may navigate on the locally stored data and thus wants a smoothly reacting App.
This interaction (fetching data from server via JSONP and after doing some minor work with it, storing it in the local database) is done asynchronously, but because of the necessary DOM manipulation for JSONP and the database interaction I am not able to this work with WebWorkers. Thus I am running in the problem in blocking the JavaScript event queue with lots of "background processing requests" and the App reacts very slowly (or even locks completely).
Here is a small sketch of the way I am doing the background stuff:
var pendingTasksOnNewObjects = new Object();
//add callbacks to queue
function addTaskToQueue(id, callback) {
if (!pendingTasksOnNewObjects[id]) {
pendingTasksOnNewObjects[id] = new Array();
}
pendingTasksOnNewObjects[id].push(callback);
}
// example for this pending tasks:
var myExampleTask = function () {
this.run = function(jsonObject) {
// do intersting stuff here as early as a specific new object arrives
// i.e. update some elements in the DOM to respect the newly arrived object
};
};
addTaskToQueue("id12345", myExampleTask);
// method to fetch documents from the server via JSONP
function getDocuments(idsAsCommaSeparatedString) {
var elem;
elem = document.createElement("script");
elem.setAttribute("type", "text/javascript");
elem.setAttribute("src", "http://serverurl/servlet/myServlet/documents/"+idsAsCommaSeparatedString);
document.head.appendChild(elem);
}
// "callback" method that is used in the JSONP result to process the data
function locallyStoreDocuments(jsonArray) {
var i;
for (i=0; i<jsonArray.length; i++) {
var obj = jsonArray[i];
storeObjectInDatabase(obj);
runTasks(obj.docID, obj);
}
return true;
}
// process tasks when new object arrives
function runTasks(id, jsonObject) {
if(pendingTasksOnNewObjects[id]) {
while(pendingTasksOnNewObjects[id][0]) {
pendingTasksOnNewObjects[id][0].run(jsonObject);
pendingTasksOnNewObjects[id].shift();
}
}
return true;
}
I already looked around reading some stuff about the event processing mechanisms in JavaScript (i.e. John Resigs description of working with timers), but what I really want to do is the following: Check if there is something to do in the event queue. If so, wait for 100ms and check again. If nothing is currently waiting in the queue, process a small amount of data and check again.
As far as I read, this is not possible, since JavaScript does not have direct access to the event queue.
So are there any design ideas on how to optimize my code to get closer to the goal not to interfere with the UI events to give the user a smooth App interaction?
Web Workers seem to be available in iOS 5, http://caniuse.com/webworkers

Categories

Resources