Changes

Jump to: navigation, search

DPS921/Web Worker API

12,007 bytes added, 04:37, 5 December 2020
m
References
</ul>
</p>
== Browser Support ==
<p>According to [https://caniuse.com/webworkers https://caniuse.com/webworkers] ''Web Workers API'' is supported on most of the modern web browsers on both desktop and mobile:</p>
[[File:DedicatedWorkerSupport.PNG]]
<p>''Shared Worker'' features a lower support rate, but is still available on a plenty of browsers:</p>
[[File:SharedWorker.PNG]]
 
== Dedicated Worker ==
Dedicated workers are usually referred to when general web workers are mentioned, hence the basic syntax mentioned above us utilized in this section. Dedicated workers are only accessible by the script that spawn them and are often represented in the DedicatedWorkerGlobalScope. Dedicated Workers are compatible with both desktop and mobile browsers. Dedicated workers provide us the capability to offload computation from the master thread to a thread running on another logical processor. We have leveraged dedicated workers in the following demo we hosted on GitHub pages:
 
[https://vitokhangnguyen.github.io/WebWorkerDemo/index.html https://vitokhangnguyen.github.io/WebWorkerDemo/index.html]
 
In the demo, we take a [https://github.com/vitokhangnguyen/WebWorkerDemo/blob/main/wallpaper.jpg 6K image] and apply a Sobel filter. This filter is used to emphasize the edges within an image. It is often used in image processing and computer vision. When we ran the serial code, we noticed the UI lagged (when scrolling) and the cursor remained in the pointer state throughout the entire filtering process, delaying the user from interacting with the user interface. We took an extensive look into this lagging using the performance tool in Firefox. We discovered that the DOM clicking event occurred throughout the entire duration of the function execution (8 seconds for my PC), and the FPS almost dropped down to 0, as shown in the image below.
 
 
[[File:Firefox Performance.PNG]]
 
 
To counter this, we used Dedicated Workers to perform the CPU intensive calculations on another thread, enabling the user to interact with the website.
 
'''[https://github.com/vitokhangnguyen/WebWorkerDemo/blob/main/scripts/dedicated.js dedicated.js]'''
 
<pre>
function performParallelSobel() {
// Reset canvas
const tempContext = parallelCanvas.getContext("2d");
tempContext.drawImage(image, 0, 0);
 
// Check if web workers are compatible (All browsers that follow HTML5 standards are compatible)
if (window.Worker) {
 
// Record starting time
let start = window.performance.now();
let end;
const numOfWorkers = slider.value;
let finished = 0;
 
// Height of the picture chunck for every worker
const blockSize = parallelCanvas.height / numOfWorkers;
 
// Function called when a worker has finished
let onWorkEnded = function (e) {
// Data is retrieved using a memory clone operation
const sobelData = e.data.result;
const index = e.data.index;
 
// Copy sobel data to the canvas
let sobelImageData = Sobel.toImageData(sobelData, parallelCanvas.width, blockSize);
tempContext.putImageData(sobelImageData, 0, blockSize * index);
 
finished++;
 
if (finished == numOfWorkers) {
// Calculate Time difference
end = window.performance.now();
const difference = `${end-start} ms`;
parallelResults.textContent = difference;
const color = '#' + Math.floor(Math.random() * 16777215).toString(16);
// Update chart
updateChart(numOfWorkers, end - start, color, `Parallel (${numOfWorkers})`);
}
};
 
// Launch n numbers of workers
for (let i = 0; i < numOfWorkers; i++) {
// Create a web worker object by passing in the file consisting of code to execute in parallel
const worker = new Worker('./scripts/dedicatedWorker.js');
// Once the worker has completed, execute onWorkEnded function
worker.onmessage = onWorkEnded;
 
// Break image into chunks using the blocksize
const canvasData = tempContext.getImageData(0, blockSize * i, parallelCanvas.width, blockSize);
 
// Start Working - (launch the thread)
worker.postMessage({
data: canvasData,
// Thread ID
index: i,
});
}
}
}
</pre>
 
The code that the worker runs is the following ('''[https://github.com/vitokhangnguyen/WebWorkerDemo/blob/main/scripts/dedicatedWorker.js dedicatedWorker.js]'''):
 
<pre>
importScripts('./sobel.js');
 
// Retrieve message (data) from the script that created the worker
self.onmessage = function (event) {
// Set worker ID
const index = event.data.index;
 
// Get data and call the Sobel filter
const sobelData = Sobel(event.data.data);
 
// Post the data back on completion
self.postMessage({ result: sobelData, index: index});
};
</pre>
 
Now running the Sobel filter on a separate thread allows us to continue interacting with the UI but, the execution time slightly increased. This is because we have to instantiate the worker, which takes time and resources. Luckily, we can drastically improve the Sobel filtering process by breaking the image down into horizontal chunks. We do this by getting the height and dividing by the number of processors allowed by the browser, which is obtained using the Windows API (''window.navigator.hardwareConcurrency''). Once we chunk the image, we can create ''n'' number of worker objects based on the hardware concurrency set by the browser and post the chunk of data to them with their ID (''index''). When a worker is finished running, it will send a response using the ''onmesasge'' event handler, so we can assign a function to be executed when the event is triggered. In the file, we passed into the web worker constructor, we refer to the global scope using self and call the ''onmessage'' event handler. This event handler receives the posted data we sent from dedicated.js and initiates the Sobel filtering. Once the filtering is done, the data is posted back by referring to self.
 
[[File:Sobel Filtering Execution Time Per Processor.PNG]]
 
Here are the results we got from running the Sobel filter on 10 processors. As we utilize more web workers, the faster the filter is applied to the image.
== Shared Worker ==
I have leveraged this knowledge to complete a demo of a 2-player Battleships game that consists of 2 browser windows - 1 for each player. Rather than having a server coordiating the 2 players, the players would coordinate themselves by sending/receiving messages to/from each other through a shared worker acting as an arbitrator.
The demo is accessible here or through the following URL:<mark>[https://vitokhangnguyen.github.io/WebWorkerDemo/shared.html https://vitokhangnguyen.github.io/WebWorkerDemo/shared.html] == Additional Web Worker's Features == === Load External Scripts === In the regular browser'scope, scripts can import to use data or functions from each other by using the <script> tag and adding them to the same HTML file. For example, by doing the following, script2 and script1 will have the same global scope: <pre><script src="/script1.js"></script><script src="/script2.js"></script></pre> However, with the web workers, that would not work because the worker processes do not load the HTML files. To achieve the same effect, the WorkerGlobalScope offers a function namely importScripts(). In the worker's source file, we can do the following so that we can access the global scope of 2 source files: script1.js, script2.js and foobar.js. <pre>importScripts("script1.js", "script2.js", "foobar.js")</pre> Notice that the function can take as many arguments as possible and the effect is going to be the same as importing one by one. === Transferable Objects === It is important to realize that in Web Worker communications, there are no real shared data! The data is transferred among the processes via message passing and the browser implements an algorithm called [https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm Structured Cloning] to perform this. In short, an object in JavaScript is recursively copied and represented in the JSON format to be parsed upon being received. The method has a weakness that if the copied object is too large in size, the process of copying and transmiting this object through a message can take very long. To remedy this issue, most modern browsers nowadays allow transferable objects which means an object would stay in memory and would not be copied while a reference to it is transferred over to the destination process, instead. The old reference to that object in the source process would no longer be available after the transfer. This makes a huge performance boost when transferring large objects. To utilize the feature, the following syntax of the Worker.postMessage() function is employed: <pre>let worker = new Worker('worker.js');worker.postMessage(data, [arrayBuffer1, arrayBuffer2]);</pre> Note that the 1st argument of the function call is the data you want to copy and send and the 2nd argument is an array of items you want to transfer. The array must only contain items of the ArrayBuffer type to be transferred. === Error Handling === Web Worker API provides the capability of handling errors in the worker processes from the main process. When an Error is raised in a worker, an ErrorEvent will be fired to the main process. We can listen to this event by assigning a function to the event handler onerror of the worker object. The following code sets up the main process to print to the console a message when an Error is raised in the worker: <pre>let worker = new Worker('worker.js');worker.onerror = function(e) { console.log(`An error occcured in the ${e.filename} worker at line %{e.lineno}`); console.log(`Error: ${e.message}`);}</markpreNote that the ErrorEvent has 3 special properties which are filename (the name of the worker's source file where the error occured), lineno (the line number in the worker's source file where the error occured) and message (a description of the error). == Other Workers == === Service Worker === As a type of Web Worker, the Service Worker is a also a JavaScript source file that runs in the background of the browser on a thread that is separated from the main thread. It acts as a proxy server between the application on the browser and the Internet. It is euqipped with utilities that help intercept network request and perform some actions with that requests. Those actions include relaying the request to its intneded destination, dropping the request, caching of the request contents or responding to the request with the cached data. In addition, it can act on the requests differently based on each of the request or the current network status. For those utilities, the Service Worker enables your applications to be capable of working offline. It allows the developer to write the strategies for that such as "network-first-then-cache", "cache-first-then-network" or "cache-only"...etc. Service Worker is an essential component of any Progressive Web App (PWA). === Chrome Worker === This type of Web Worker allows the developer to write privileged codes by giving them accesses to low-level functions. Specifically, the worker gives us access to [https://developer.mozilla.org/en-US/docs/Mozilla/js-ctypes js-ctypes] that allows an application to extend or call native codes written in C or C++. This feature is not standardized and might be or already have been removed from many browsers.
= References =
* <ol><li>HTML Living Standard, Web Worker sectionWorkers: https://html.spec.whatwg.org/multipage/workers.html. Last updated: 2 Dec. 2020.</li>* <li>MDN Web Docs, Using Web Worker API: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API. Last updated: 26 Aug. 2020.</li><li>Can I use..., Support tables for HTML5, CSS3, etc, Web Workers: https://caniuse.com/webworkers. Last updated: 30 Nov. 2020.</li> <li>Can I use..., Support tables for HTML5, CSS3, etc, Shared Workers: https://caniuse.com/sharedworkers. Last updated: 30 Nov. 2020.</li> <li>HTML5Rocks, The Problem: JavaScript Concurrency: https://www.html5rocks.com/en/tutorials/workers/basics/. Last updated: 26 Jul. 2020.</li><li>MDN Web Docs, Service Worker API: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_APIService_Worker_API. Last updated: 16 Nov. 2020.</li><li>MDN Web Docs, ChromeWorker: https://developer.mozilla.org/en-US/docs/Mozilla/Gecko/Chrome/API/ChromeWorker. Last updated: 18 Feb. 2020.</li></Using_web_workersol>

Navigation menu