By David Rousset
This could be pretty annoying with today’s multi-core processors like the i5/i7 containing up to 8 logical CPUs—and even with the latest ARM mobile processors being dual or even quad-cores. Hopefully, we’re going to see that HTML5 offers the Web a better way to handle these new, marvelous processors to help you embrace a new generation of Web applications.
Before the Workers…
We all know the bad consequences of overloading this thread: the page freezes and the user can’t interact with your application any more. The user experience is then, of course, very unpleasant, and the user will probably decide to kill the tab or the browser instance. Probably not something you’d like to see happen to your app!
To avoid that, browsers have implemented a protection mechanism which alerts users when a long-running suspect script occurs.
Unfortunately, this mechanism can’t tell the difference between a script not written correctly and a script that just needs more time to accomplish its work. Still, as it blocks the UI thread, it’s better to tell you that something wrong is maybe currently occurring. Here are some message examples (from Firefox 5 & IE9):
Up to now, those problems were rarely occurring for 2 main reasons:
Those ways are well-known to all Web developers. For instance, we were trying to simulate parallel tasks thanks to the setTimeout() and setInterval() methods. HTTP requests can also be done in an asynchronous manner, thanks to the XMLHttpRequest object that avoids freezing the UI while loading resources from remote servers. At last, the DOM Events let us write applications giving the illusion that several things occur at the same time. Illusion, really? Yes!
To better understand why, let’s have a look at a fake piece of code and see what happens inside the browser:
Let’s take this code to project it on a model. This diagram shows us what’s happening in the browser on a time scale:
This diagram well-illustrates the non-parallel nature of our tasks. Indeed, the browser is only enqueuing the various execution requests:
All these tips don’t really solve our initial problem: everything keeps being executed inside the main UI thread.
Web Workers or how to be executed out of the UI Thread
My 1st Web Worker
As Web Workers will be executed on separated threads, you need to host their code into separated files from the main page. Once done, you need to instantiate a Worker object to call them:
You’ll then start the worker (and thus a thread under Windows) by sending it a first message:
Indeed, the Web Workers and the main page are communicating via messages. Those messages can be formed with normal strings or JSON objects. To illustrate simple message posting, we're going to start by reviewing a very basic sample. It will post a string to a worker that will simply concatenate it with something else. To do that, add the following code into the “helloworker.js” file:
We’ve just defined inside “helloworkers.js” a piece of code that will be executed on another thread. It can receive messages from your main page, do some tasks on it, and send a message back to your page in return. Then we need to write the receiver in the main page. Here is the page that will handle that:
The result will be: “Hello David from a separate thread!” You’re impressed, aren’t you? Description: Clignement d'œil
Be aware that the worker will live until you kill it.
Since they aren’t automatically garbage collected, it’s up to you to control their states. And keep in mind that instantiating a worker will cost some memory…and don’t negligate the cold start time either. To stop a worker, there are 2 possible solutions:
DEMO: You can test this slightly enhanced sample in your browser here: https://david.blob.core.windows.net/html5/HelloWebWorkers_EN.htm
Posting messages using JSON
Of course, most of the time we will send more structurated data to the Workers. (By the way, Web Workers can also communicate between each other using Message channels.)
But the only way to send structurated messages to a worker is to use the JSON format. Luckily, browsers that currently support Web Workers are nice enough to also natively support JSON. How kind they are!
Let’s take our previous code sample. We’re going to add an object of type WorkerMessage. This type will be used to send some commands with parameters to our Web Workers.
Let’s use the following simplified HelloWebWorkersJSON_EN.htm Web page:
At last, here is the code for the Web Worker contained in helloworkerJSON_EN.js the file:
Once again, this sample is very basic. Still, it should help you to understand the underlying logic. For instance, nothing prevents you to use the same approach to send some gaming elements that will be handled by an AI or physics engine.
DEMO: You can test this JSON sample here: https://david.blob.core.windows.net/html5/HelloWebWorkersJSON_EN.htm
Web Workers have just arrived in the IE10 Platform Preview. This is also supported by Firefox (since 3.6), Safari (since 4.0), Chrome & Opera 11. However, this is not supported by the mobile versions of these browsers. If you’d like to have a more detailed support matrix, have a look here: https://caniuse.com/#search=worker
In order to dynamically know that this feature is supported in your code, please use the feature detection mechanism. (You shouldn’t use some user-agent sniffing!)
To help you, there are 2 available solutions. The first one is to simply test the feature yourself using this very simple piece of code:
The second one is to use the famous Modernizr library (now natively shipped with the ASP.NET MVC3 project templates). Then, simply use a code like that:
Here, for instance, is the current support in your browser: Web Workers are not supported inside your browser.
Non-accessible elements from a worker
Rather than looking at what you don’t have access to from Workers, let’s take a look at what you only have access to:
In summary, you don’t have access to the DOM. Here is a very good diagram summarizing that:
For instance, since you don’t have access to the window object from a worker, you won’t be able to access the Local Storage (which doesn’t seem to be thread-safe anyway). Those limitations may look too constraint for developers used to multi-threaded operations in other environments. However, the big advantage is we won’t fall into the same problems we usually encounter: lock, races conditions, etc. We won’t have to think about that with Web Workers. This makes the Web Workers something very accessible, while allowing some interesting performance boosts in specific scenarios.
Error handling & debugging
It is very easy to handle errors raised from your Web Workers. You simply have to subscribe to the OnError event in the same way we’ve done it with the OnMessage event:
This is the best Web Workers can give you natively to help you debugging their code… This is very limited, isn’t it?
The F12 development bar for a better debugging experience
To go beyond that, IE10offers you to directly debug the code of your Web Workers inside its script debugger like any other script.
For that, you need to launch the development bar via the F12 key and navigate to the “Script” tab. You shouldn’t see the JS file associated to your worker yet. But right after pressing the “Start debugging” button, it should magically be displayed:
IE10 is currently the only browser offering you that. If you want to know more about this feature, you can read this detailed article:Debugging Web Workers in IE10
An interesting solution to mimic console.log()
At last, you need to know that the consoleobject is not available within a worker. Thus, if you need to trace what’s going on inside the worker via the .log() method, it won’t work as the console object won’t be defined. Hopefully, I’ve found an interesting sample that mimics the console.log() behavior by using the MessageChannel: console.log() for Web Workers. This works well inside IE10, Chrome & Opera but not in Firefox as it doesn’t support the MessageChannel yet.
Note: In order to make the sample from this link work in IE10, you need to change this line of code:
By this one:
Then, you should be able to obtain such results:
DEMO: If you want to try this console.log() simulation, navigate here -> https://david.blob.core.windows.net/html5/HelloWebWorkersJSONdebug.htm <-
Use cases and how to identify potential candidates
Web Workers for which scenarios?
It’s true that the limitations we’ve seen above on the resources available inside Web Workers narrow down the number of interesting scenarios. Still, if you just take some time to think about it, you’ll start to see new interesting usages:
Moreover, if you switch to the video game world, you can think about pushing the AI or physics engines to the Web Workers. For instance, I’ve found this experimentation: On Web Workers, GWT, and a New Physics Demo which use the Box2D physic engine with Workers. For your Artificial Intelligence engine, this means also that you will be able in the same timeframe to process more data (anticipate more moves in a chess game for instance).
Some of my colleagues may now argue that the only limit is your imagination! Description: Clignement d'œil
On our side, we’ve recently published the demo named Web Workers Fountains:
This demo displays some particles effects (the fountains) and uses 1 Web Worker per fountain to try to compute the particles in the fastest way possible. Each Worker result is then aggregated to be displayed inside the <canvas> element. Web Workers can also exchange messages between them via the Message Channels. In this demo, this is used to ask to each of the Workers when to change the color of the fountains. We’re then looping through this array of colors: red, orange, yellow, green, blue, purple, and pink, thanks to the Message Channels. If you’re interested in the details, jump into the LightManager() function of the Demo3.js file.
Also, feel free to launch this demo inside Internet Explorer 10, it’s fun to play with! Description: Sourire
How to identify hot spots in your code
To track the bottlenecks and identify which parts of your code you could send to the Web Workers, you can use the script profiler available with the F12 bar of IE9/10. It will then help you to identify your hot spots. However, identifying a hot spot doesn’t mean you’ve identified a good candidate for Web Workers. To better understand that, let’s review together two different interesting cases.
Case 1: Animation inside <canvas> with the Speed Reading demo
This demo comes from IE Test Drive and can be browsed directly here: Speed Reading. It tries to display as fast as possible some characters using the <canvas> element. The goal is to stress the quality of the implementation of the hardware acceleration layer of your browser. But going beyond that, would it be possible to obtain more performance by splitting some operations on threads? We need to achieve some analysis to check that.
If you run this demo inside IE9/10, you can also start the profiler within a couple of seconds. Here is the kind of results you’ll obtain:
If you’re sorting the time-consuming functions in decreasing order, you’ll clearly see those functions coming first: DrawLoop(), Draw() and drawImage(). If you’re double-clicking on the Draw line, you’ll jump into the code of this method. You’ll then observe several calls of this type:
A quick conclusion of this brief analysis is that this demo spends most of its time drawing inside the Canvas through the drawImage() method. As the <canvas> element is not accessible from a Web Worker, we won’t be able to offload this time-consuming task to different threads (we could have imagined some ways of handling the <canvas> element in a concurrency manner for instance). This demo is then not a good candidate for the parallelization possibilities offered by the Web Workers.
But it’s well-illustrating the process you need to put in place. If, after some profiling job, you’re discovering that the major part of the time-consuming scripts are deeply linked to DOM objects, the Web Workers won’t be able to help you boost the performance of your Web app.
Case 2: Raytracers inside <canvas>
Let’s now take another easy example to understand. Let’s take a raytracer like this one: Flog.RayTracer Canvas Demo. A raytracer uses some very CPU-intensive mathematical computations in order to simulate the path of light. The idea is to simulate some effects like reflection, refraction, materials, etc.
Let’s render a scene while launching the script profiler. You should obtain something like this:
Again, if we sort the functions in decreasing order, 2 functions clearly seem to take most of the time: renderScene() and getPixelColor().
This is then not a surprise if we can find some raytracers samples using some Web Workers like this one: https://nerget.com/rayjs-mt/rayjs.html
After profiling this raytracer using IE10, we can see the important differences between using no Worker and using 4 Workers:
In the first screenshot, the processRenderCommand() method is using almost all of the CPU available and the scene is rendered in 2.854s.
With 4 Web Workers, the processRenderCommand() method is executed in parallel on 4 different threads. We can even see their Worker Id on the right column. The scene is rendered this time in 1.473s. The benefits were real: the scene has been rendered 2 times faster.
Here are some interesting additional resources to read:
About the Author