Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

Web Workers

5.00/5 (3 votes)
26 Jul 2012CPOL9 min read 19.9K  
A Chapter excerpt from HTML5 for .NET Developers
HTML5 for .NET Developers
By Jim Jackson II and Ian Gilman

In any HTML application with a significant amount of user interaction, you will encounter requirements that conflict with one another. This article based on chapter 8 of HTML5 for .NET Developers talks about merging the need for a responsive user interface with the requirement to perform complex, processor-intensive tasks.

You may also be interested in ...

Web workers are a very simple set of interfaces that allow you to start work on a thread owned by the browser but different from the thread used to update the interface. This means true asynchronous programming for JavaScript developers. Current methods of yielding a thread from one task to another is done automatically by the browser’s JavaScript engine but the result, while often blazingly fast, is not truly concurrent processing, where two things are working simultaneously. Figure 1 shows the traditional methodology.

Figure 1 Earlier versions of HTML and JavaScript allowed only one thread to perform work for the application. If a callback or an event was received, the current thread would have to yield for that work to be processed.

This methodology was fine for simple applications and, as the speed of JavaScript engines increased, it even works for many more complex applications. A tremendous amount of research and development has been done to get every last ounce of performance out of traditional JavaScript applications.

TIP Refer to Nicholas Zakas’ High Performance JavaScript by O’Reilly for insights into how to get the most from a single thread in JavaScript.

The problem with the single thread, as you have probably realized, is that if you are using canvas to build an image or calling a server to round-trip a large data packet, there is really nothing you can do that will not slow down your user interface, your complex task or both. As the client side of web applications gets more complex, you will find that single thread is just not enough anymore. Figure 2 shows what the new world of web workers looks like.

Figure 2 Using web workers, the application can spawn a new thread and perform work there, communicating with the primary thread as needed  without affecting user interface performance.

A web worker’s lifetime can last only as long as your current page exists in the browser. If you refresh the page or navigate to a different page, that worker object will be lost and need to be restarted from scratch if you want to have access to it again.

Rules for Web Workers

A web worker will execute inside its own thread in the browser. It has access to server resources and any data passed to it from the host thread. It also has the ability to start its own workers (called subworkers) and will then become the host thread for those workers. A web worker can also access the navigator object which means it can communicate with the geolocation API. It can make its own ajax calls and access the location object.

The web worker cannot access any part of the user interface. That means that anything inside the window object is off-limits as is the document object. In a JavaScript application, any variables declared are automatically in the global scope, meaning they are attached to window. So your web worker will not be able to access any application variables, functions or respond to any events or callbacks from the rest of your application.

Tip: Because the worker object cannot access any user interface elements, it is generally not advisable to pull in the jQuery library. jQuery is designed to run on the UI thread so it will immediately throw exceptions when loaded into a process that does not contain a window object.

A web worker uses a single JavaScript file to execute and, within that script, can pull in other files when it starts up. This opens the possibility for having your entire application logic reside on a thread separate from the user interface thread but that is well outside the scope of this chapter.

The specification states that web workers, by design, are not intended for short-term execution and should not be counted upon to be available after starting up. This means that the results of work you send to a web worker should not have user interface dependencies. Web workers are useful but heavy so try not to use a big hammer on a small nail.

Sending Work to Another Thread

A web worker is not a static object that is just available, like geolocation. Rather, it is an object that you create and reference with a variable. You do that by calling new on the Worker object and passing it a script file. The file should be referenced by a complete URL or by a location relative to the current page location in your web site.

C#
var myWorker = new Worker('WorkerProceses.js');

Once created, the worker will not time out or stop its work unless told to do so via the worker.terminate function. Because the JavaScript is immediately executed into memory as soon as the script file is loaded, the worker can begin performing work such as making calls to geolocation or making ajax calls for data. It can also start sending messages back to its parent thread immediately.

The host’s worker object has a postMessage(string) method it can use to pass regular strings or JSON data to the worker. The worker also has a message event that can be wired up using either the addEventListener function or by wiring a function to the onmessage event. Likewise, inside the worker script, you can call postMessage and attach to the message event. The host process also has a worker.terminate function that stops all work being performed by the worker object. Once terminate is called, no additional messages will be sent or received by the worker object.

A Web Work Example

Create a new web application. Add an index.html page, a Scripts folder and two JavaScript files inside it. Name one main.js and the other myWorker.js. Inside the index page, add a script reference to the latest version of jQuery at the top and a reference to scripts/main.js at the bottom before the closing </body> tag. Next, add an input button to test our functionality. <input type=button id="testWorkers" value="Test Workers" />

Finally, inside main.js, add the following code to handle document.ready and to create a simple object to contain our test code.

Listing 1 Creating a worker and sending messages to it

PHP
$(document).ready(function () {
   workerTest.init();                                      #A
});
window.workerTest = {
   myWorker: null,
   init: function () {
      self = this;
      self.myWorker = new
Worker("/Scripts/myWorker.js");         #B
      $("#testWorkers").live("click", 
         function () {
            self.myWorker.postMessage("Test");                    #C
         }
      );
      self.myWorker.addEventListener("message",
         function (event) {
            alert(event.data);                             #D
         }, 
      false);
   }
};
#A Initializes our object using the regular jQuery ready function.
#B Creates a new worker object and assign it as a local variable.
#C Sends the worker a message whenever the test button is clicked.
#D When the worker sends a message back to the host, notify the user.

In the worker object, we will show that the JavaScript begins executing as soon as the script is loaded and that it can begin immediately sending messages and responding to posts.

Listing 2 A simple web worker script example

C#
count = 0;                                          #A
init = function () {
   self.count++;                                    #B
   self.postMessage("start count: " + count);       #C
}
self.addEventListener("message", function (event) {
   self.count++;                                    #D
   setTimeout(function () {
      self.postMessage("Last Msg: " + event.data +  #E
         ", count: " + count);                      #E
   }, 1000);
}, false);
init();                                             #F
#A Since we do not have access to window, we cannot
assign variables to it by default. Variables here are scoped to the script
file.
#B Here, we update the count value to show that work
has been done.
#C Upon initialization, we can call the postMessage
event proving that messages can be sent not in response to any host request.
#D Update the count variable whenever a message is
received.
#E When a message is received, wait one second and respond with the message
sent and the current count value.
#F At the end of the script, we call the init function to start performing
work.

The overall interface between a host application and a web worker is very simple, as you can see. The primary difference is that the host calls functions on its worker variable, while the client calls the same functions on self.

Figure 3 The host process can post and receive messages using the same methods as the client except that the host calls these methods on the worker object while the client process uses the self keyword to execute these functions.

The MessageEvent Object

When receiving a message in either direction, the returned object is a MessageEvent object. It has a data property that is a copy of the value posted, not a reference to the original piece of data. In addition, it has a short list of properties that are used for cross-document messaging. Cross-document messaging is a specification that allows scripts from other domains to communicate.

Including Other Scripts

Bringing in other scripts is very easy inside a worker script. You simply call the importScripts function using the name of the script file or files to include. Keep in mind that the script locations will be relative to the current worker script’s location in the site. If you need to import multiple scripts, use a single importScripts function call with comma separated script names. importScripts("inclA.js", "inclB.js")

In this code, you could define variables varA and varB and they would immediately be available inside the worker script since they will also be immediately executed. The order of the scripts in the list will also be honored so dependencies can be built up. If you need to load scripts conditionally you can also call importScripts multiple times from anywhere in the code with different scripts or using conditional logic to load different scripts. Finally, you can load the same script multiple times but be careful since any variables initialized in the external script and changed in your main worker process will be reset when the script is reloaded, not to mention the fact that the script will be reloaded from the server each time.

Ending the Worker Process

We mentioned that a web worker can be stopped by calling terminate. When this happens, no further messages are received or sent from the worker process. This is a true disposal of the worker process. Although calling postMessage against the worker will not throw an exception, no work will be done. Also, inside your worker process, any timers or other code that is executing will be immediately stopped.

Web Worker Errors

When an exception is thrown from the web worker, it is handled in the host using the error event. The resulting event object has the following properties:

  • message—Actual error message encountered in the script.
  • filename—File name where the exception occurred.
  • lineno—Line number in the script file where the exception was encountered.

Summary

While the browser can certainly access the host system’s memory and processor resources, doing any heavy processing will often cause the screen to hang for a few moments. You may even encounter a browser message stating that it thinks you have a runaway process. The Web Worker specification solves this problem by allowing you to create a background worker that can do work on a different thread, thus freeing up the user interface thread to perform screen reflows and other more interactive logic.

Quick & Easy HTML5 and CSS3
Rob Crowther

Sass and Compass in Action
Wynn Netherland, Nathan Weizenbaum, and Chris Eppstein

Secrets of the JavaScript Ninja
John Resig and Bear Bibeault

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)