Implementing JavaScript worker threads
From Trephine
| « Thread safe DOM manipulation | Alternative JavaScript worker thread API » |
[subscribe] Recent blog entries
- Simple prototypal inheritance new!
- Adventures in Rhino - setters and getters
- Site improvements - fighting with Disqus
- JavaScript task chaining
- JavaScript string building benchmarks
- Efficient JavaScript string building
- Alternative JavaScript worker thread API
- Implementing JavaScript worker threads
- Thread safe DOM manipulation
- Site improvements - CSS sprites
- Trephine worker threads made easy
- Pitfalls of multithreaded browser development
- Site improvements - reducing dependencies
- The unsplittability of XML
Live Demos
Implementing JavaScript worker threads
The HTML 5 specification promises to bring a new level of sophistication to web applications by introducing the concept of worker threads. In fact, some browsers such as Firefox 3 and Safari 4 (still in beta at the time of this writing) already support them. Unfortunately, it will mostly likely be many years before HTML 5 is widely supported (greater than say 75% market share).
The good news is that it's possible to have JavaScript worker threads today with trephine. This article provides the necessary support code, including ready-made examples you can use in your own project.
Last week, we explored some of the pitfalls of multithreaded browser development, showed how to easily create trephine worker threads, and discussed using threads to safely manipulate the DOM. Here we'll pull all these ideas together into a cohesive model for seamlessly implementing JavaScript worker threads.
Recall that we had come up with this code for adding a run() method to trephine/Rhino JavaScript functions:
Function.prototype.run = function run() { var self = this, args = []; for (var i=0, l=arguments.length; i<l; i++) args[i] = arguments[i]; var thread = new java.lang.Thread( new java.lang.Runnable({ run: function() { self.apply(null, args); } }) ); thread.start(); return thread; };
And that the usage pattern was this:
function someFunction( /* parameters */ ) { // ... operate on parameters } someFunction.run( params );
In the above snippet, we define someFunction() and then launch it as the body of a new anonymous Java worker thread.
Also recall that we developed an eval() method to aid trephine/Rhino JavaScript in making calls to the browser DOM environment:
Function.prototype.eval = function eval() { var args = []; for (var i=0, l=arguments.length; i<l; i++) args[i] = arguments[i]; var code = [ '(', this, ').apply(null,', JSON.stringify(args), ')' ].join(''); return window.eval( code ); };
Which is used like so:
function someFunction( /* parameters */ ) { // ... operate on params, ex: alert( arguments[0] ); } someFunction.eval( params );
All of the above code is meant to be executed in the trephine/Rhino context, which is a little less convenient than browser based JavaScript since it requires paying some attention to the execution context of a given bit of code.
With a bit more magic, we can remove this pain point by implementing an equivalent run() method for functions which exist in the browser JavaScript environment:
Function.prototype.run = function run() { var args = []; for (var i=0, l=arguments.length; i<l; i++) args[i] = arguments[i]; var result = trephine.js( function(func, args) { var func = eval(func); Function.prototype.run.apply(func, args); }, '(' + this + ')', args ); };
To give an example, consider this code which defines a worker function, then executes it. This code is executed from the browser JavaScript environment, and so requires no specific calls to trephine's API directly:
function roundTrip( message, duration ) { java.lang.Thread.currentThread().sleep( duration ); function domCallback( msg ) { alert( msg ); } domCallback.eval( message ); }; roundTrip.run( "Whoa, I know Kung Fu!", 1000 );
And of course we can simplify this by replacing the named functions with anonymous ones:
(function ( message, duration ) { java.lang.Thread.currentThread().sleep( duration ); (function ( msg ) { alert( msg ); }).eval( message ); }).run( "Whoa, I know Kung Fu!", 1000 );
The above code handles declaring and executing a worker function which in turn executes code back in the browser JavaScript environment. Not too shabby for 4 lines of contiguous code. The equivalent gesture using HTML 5 workers would require storing the worker logic in a separate js file and necessitate the use of message passing in order to affect the DOM.
In fact, I'm fairly confident that it would be possible to implement the HTML 5 Web Workers spec. Even without this, there are ways of making workers easier to create and manipulate by introducing a little more structure, which will likely be the topic of a future article.
As a wrap up, here is the collected code from above, in an easy to execute copy-paste ready format:
trephine.js( function(){ Function.prototype.run = function run() { var self = this, args = []; for (var i=0, l=arguments.length; i<l; i++) args[i] = arguments[i]; var thread = new java.lang.Thread( new java.lang.Runnable({ run: function() { self.apply(null, args); } }) ); thread.start(); return thread; }; Function.prototype.eval = function eval() { var args = []; for (var i=0, l=arguments.length; i<l; i++) args[i] = arguments[i]; var code = [ '(', this, ').apply(null,', JSON.stringify(args), ')' ].join(''); return window.eval( code ); }; } ); Function.prototype.run = function run() { var args = []; for (var i=0, l=arguments.length; i<l; i++) args[i] = arguments[i]; var result = trephine.js( function(func, args) { var func = eval(func); Function.prototype.run.apply(func, args); }, '(' + this + ')', args ); };
Public domain declaration
Just so there's no confusion: all of the code snippets on this page are provided "AS IS", without warranty of any kind, express or implied.
All of the code snippets on this page are hereby released into the public domain by the me, the copyright holder. This applies worldwide. Or in case this is not legally possible: The copyright holder grants any entity the right to use this work for any purpose, without any conditions, unless such conditions are required by law.
If you'd feel better with a "real" license, you're free to use code snippets on this page under the MIT license as described on the about page.
Any links back to this site are always appreciated, but not required. Enjoy!
--Jim R. Wilson (jimbojw) 22:13, 27 April 2009 (UTC)