Multithreading in web browser (Web Workers + jQuery)

This post is a kind of expe­ri­ment. It’s not a strict trans­la­tion of my previous post writ­ten in polish. I’m just curious how „uncle Google” will behave and if he send me some visi­tors :-) Notice that english is my second langu­age, so you will have to bear some my langu­age mista­kes. In case this project succe­edes, I will write some more english stuff.

I’m going to write about one of HTML5 new featu­res — Web Workers. These are inten­ded to provide some heavy compu­ta­tions in the back­gro­und and — what is most impor­tant — web brow­ser rema­ins respon­sive. It means that you won’t expe­rience any slowe­ness. When Web Worker ends its job, it noti­ces brow­ser about the outcome. The point is to use user’s CPU which is rarely full used in oppo­site to server’s.

By the way, multi­th­re­ading in brow­ser is not a big deal, Face­book uses it to provide you instant messa­ging or wall refre­shing without relo­ading the whole page.

Offi­cial draft of Web Workers API informs that workers have many restric­tions, such as no access to DOM or (in our case) jQuery object. You may think of it as a closed envi­ron­ment. Howe­ver you are able to commu­ni­cate in both ways via JSON and post­Mes­sage() function.

Take a look at our goal. We can control spaw­ning of new Workers, it state update inte­rvals and the moment of termi­na­tion. To ensure that our brow­ser supports Web Workers, we’ll use nice library specia­li­zed in chec­king a lot of modern HTML featu­res — Moder­nizr. In both cases user reads alert message if he can use workers or not.

Basicly we need two files: worker and main html file. Below is Java­Script code of timeworker.js:

var workerNo;
 
onmessage = function(e) {
    workerNo = e.data.workerNo;
    changeInterval(e.data.timer);
}
 
function changeInterval(interval) {
    setInterval("gimmeTime()",interval);
}
 
function gimmeTime() {
    postMessage({
        time:new Date().toUTCString(),
        workerNo:workerNo
    });
}
  1. Field wor­kerNo conta­ins worker id — inte­ger 1 or greater.
  2. onmes­sage is message recie­ved from the brow­ser. Notice that we can handle recie­ved data via e.data, not e alone!
  3. chan­ge­In­te­rval is func­tion which self calls gimme­Time() func­tion with given para­me­ter inte­rval (in microsecs).
  4. gim­me­Time sends brow­ser JSON message with its id and current time via post­Mes­sage() function.

Now it’s time for workersdemoeng.html (I’m listing only JS code):

var workers = new Array();
var counter = 0;
var bindedWorkers = '';
 
if (Modernizr.webworkers) {
    alert("Your browser supports Web Workers technology, you're free to test it!");
}
else {
    alert('Your browser does NOT support Web Workers technology. Install yourself a decent browser (FF, Chrome, Opera[?])!');
}
 
function addNotification(workerno) {
    var msg = $('<p>').appendTo('#notif');
    msg.text('Worker '+workerno+' updated state');
    msg.fadeOut(1500);
}
 
function fireUpWorker() {
    ++counter;
    var workerDiv = $('<div>').attr('id','workerdiv-'+counter).appendTo('#counters');
    var par = $('<p>').attr('id','worker-'+counter).appendTo(workerDiv);
    $('<input />').attr({
        type:'submit',
        value:'terminate',
        onClick:'terminateWorker('+counter+');return false;'
    }).appendTo(workerDiv);
    workers[counter] = new Worker('timeworker.js');
    var timer = $('#intrv').val();
    workers[counter].postMessage({timer:timer,workerNo:counter});
    bindWorkers();
}
 
function terminateWorker(workerNo) {
    workers[workerNo].terminate();
    $('#worker-'+workerNo).text('terminated!');
    $('#workerdiv-'+workerNo).fadeOut(5000);
}
 
function bindWorkers() {
    bindedWorkers += "workers["+counter+"].onmessage = function(event) {";
    bindedWorkers += "$('#worker-"+counter+"').text('Worker '+event.data.workerNo+' date : '+event.data.time);";
    bindedWorkers += "addNotification(event.data.workerNo);";
    bindedWorkers += "};";
    eval(bindedWorkers);
}
  1. We have 3 global fields: array wor­kers which holds all active workers, workers coun­ter and bin­de­dWor­kers which conta­ins ugly JS code to be execu­ted by eval() — the only way I know to force brow­ser to listen all workers, not only the last one created.
  2. add­No­ti­fi­ca­tion shows short 1,5 sec. noti­fi­ca­tion about worker state update.
  3. fireU­pWor­ker is the main func­tion. Firstly it creates a new div, and then para­graph and termi­nate button. Newly created worker rece­ives JSON message about its number and state update inte­rval. At the end, bin­dWor­kers() is executed.
  4. ter­mi­na­te­Wor­ker firstly kills certain wor­ker by worker.terminate(), and then hides div rela­ted to it.
  5. Finally the func­tion I’m least satis­fied of: bin­dWor­kers(). It handles what brow­ser should do with the rece­ived message from a worker. It works analo­gi­cally like the worker’s one: thro­ugh onmes­sage and e.data. To bin­de­dWor­kers string we’re appen­ding the code and execu­ting by eval(). The worst part is when we termi­nate some workers, this string conta­ins useless data (we could cut this out by some rege­xps, but I’m lazy ;-) ).

I have a big requ­est for the JS geeks: how to run onmes­sage code in a loop and without eval?

P.S.: It seems that I have found a bug in Chrome brow­ser: an attempt to add onclick attri­bute by jQuery didn’t work. You should use onClick instead :-)

Podobne wpisy:

  1. Wielo­wąt­ko­wość w prze­glą­darce (Web Workers + jQuery)
  2. Creating EAN-13 barcode using Coffe­eScript and HTML5 Canvas
  3. PHPowe cieka­wostki składniowe
  4. MySQL i auto­ma­tyczne tworze­nie histo­rii rekordu w bazie [cz. 1/2]

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.

Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <p> <pre lang="" line="" escaped=""> <q cite=""> <strike> <strong>