Although the technology has been around for a while now, HTML5 Web Workers didn’t draw my attention until a few days ago. As I’m building a client facing SaaS application for my client, performance is crucial. Having worked with asynchronous methods in C# for years, I thought of using this concept in JavaScript. As it turns out, multithreading in Javascript isn’t really part of the package. You had to work with timeouts and callbacks to implement some kind of asynchronous architecture, but splitting some heavy work into multiple threads (and even background work for that matter) wasn’t really supported out of the box – until HTML5 Web Workers were introduced. In this article, I’ll explore the capabilities in two different ways in the context of a ASP.NET MVC 5 application. What I’m not going to cover is the theoretical aspects of web workers.
As I’m not an expert on this domain, I’ll let others do this for me, like this one. I’m going to stick to what Web Workers were designed to do in the first place (as quoted from w3schools):
When executing scripts in an HTML page, the page becomes unresponsive until the script is finished. A web worker is a JavaScript that runs in the background, independently of other scripts, without affecting the performance of the page. You can continue to do whatever you want: clicking, selecting things, etc., while the web worker runs in the background.
Seems pretty clear to me: it is a (distant) relative of the good old BackgroundWorker in .NET. Having established what it does, let’s see how it works. The process consists out of the following activities:
- Feature check: does the current browser support Web Workers?
- Create an instance of the Worker class and add a reference to the script that must be executed upon firing the worker object. This can either be:
- An external script
- An inline script
- Subscribe and publish messages on both front- and background thread
- Trigger the process
This still looks pretty vague, let’s dive into the code right away. Find a spot in your code where you want to do some heavy work, and then have a look at this code:
Next thing to do is to subscribe to the callback that the background method will fire when it has done its work. In Web Workers, you need to do this by writing a function on the onmessage event. The callback takes 1 parameter, but it’s a flexible one: you can add custom data in the e.data property. I’ll show you how to do this in a minute. Last thing to do here is to trigger the (background) process: the worker’s postMessage method does just this. You can add your own parameters to this method, the web worker will convert the parameter and it will be added to the data property of the incoming parameter (= which is a MessageEvent object) in the background thread. If it’s not clear yet, don’t worry – we’ll get to it. Let’s go to the code that will be run in the background (the external script):
If you look at this code, you might have noticed this establishes two-way communication between foreground and background. The foreground triggers a message, which is handled by the background thread. As soon as the latter has done its work, it can send a message back to the foreground. With the callback method in the foreground thread, it is notified of this event, upon which additional logic can be entered.
This is how the process looks like graphically: Note that the custom parameter in both postMessage methods can be accessed in the onMessage method inside the data property. Here you can see the message event in action:
At this point, I should point out there is an alternative way of implementing this feature. It is not required to have an external script, it can be done inline – but it requires some funny code. Anything else remains intact – it’s just the location where you host this code that is different. Instead of writing code in a separate Javascript file, you can add an inline script, like so:
There are no differences here code wise, but note the ID and the type of the script, we’ll need this when we declare the worker object:
With this new approach, you can add an inline script anywhere in your code, as long as you wrap it around a script tag with a unique ID. I wouldn’t really recommend this approach but at least it’s a possibility. The first approach is a lot cleaner but it has one little drawback: you need to specify a path to a script, which is a bit of a no no in ASP.NET MVC where basically everything happens through routes. There are ways to make this generic, but you’ll need to do the work yourself of course.