HTML5 / Producer-consumer-problem solved with WebWorkers


HTML5 provides the mechanism called “WebWorkers”. This feature is very nice and you can learn more about it here: http://www.html5rocks.com/en/tutorials/workers/basics/. There is also a new feature called “MessageChannel” which is meant to be used for the communication among the different origins (http://somedomain and https://somedomain:8080). I think this article is useful to learn more about it: http://www.webgranth.com/html5-web-messaging-fresh-tutorial-for-cross-document-messaging.

This post is going to create two WebWorkers and let them communicate with each other through a MessageChannel. Moreover, the WebWorkers implement the Producer-consumer-problem. The WebWorker #1 is the producer. It generates Fibonacci numbers and posts the result to the WebWorker #2. The WebWorker #2 receives the numbers and removes some of them. When it’s done with the work it posts the result back to the WebWorker #1. The WebWorker #1 fills some numbers again and posts them back to the WebWorker #2 and so on…

See it in action here: http://producer-consumer-webworker.herokuapp.com/index.html

Fork it on GitHub: https://github.com/jepetko/webworker

This post is focused on the communication between the both WebWorkers. I think this feature can be pretty handy when it comes to the separation of concerns. The WebWorker #1 is doing something different from the WebWorker #2 but they need to communicate with each other. This is what a MessageChannel cares for.

A MessageChannel instance provides two ports. The first one is associated with the WebWorker #1 (named producer.js). The second one is associated with the WebWorker #2 (named consumer.js).

The initialization of WebWorkers with the ports and the communication between them is display in the following sequence diagram:
sequencediagram

By now, let’s get down to the source code:

  • The main.js creates the WebWorkers and the MessageChannel like this:

    //... code
                    this.channel = new MessageChannel();
                    this.producer = new Worker('js/producer.js');
                    this.consumer = new Worker('js/consumer.js');
    //... code
    
  • The main.js adds event handlers to the WebWorkers. The interpreter will step in when main.js is being notified by one of the WebWorkers. Code:

                    var evtHandler = $.proxy( function(e) {
                         //... code
                    }, this );
    
                    this.producer.addEventListener('message', evtHandler, false);
                    this.consumer.addEventListener('message', evtHandler, false );
    

    Note: instead of using the $.proxy method of jQuery (which is used to execute the event handler in this scope) you can use any function like this:

                    function eventHandler() {
                         //... code
                    }
    
  • The main.js sends a message by calling postMessage which will trigger an event registration in the particular WebWorker.

                    this.producer.postMessage({ action : 'start'}, [this.channel.port1]);
                    this.consumer.postMessage({ action : 'start'}, [this.channel.port2]);
    
  • The 1st WebWorker producer.js contains an event handler onMessage too. This enables it to receive the message { action : 'start'} in order to register an additional (!) event handler port.onmessage:

    self.onmessage = function(e) {
        var port = e.ports[0];
        //register event handler on the received port to use the MessageChannel for the further communication
        if( !port.onmessage ) {
            port.onmessage = function(e) {  //onmessage handler AGAIN!!!!!!!!!!
                var action = e.data.action;
                //.... more code
                switch(action) {
                    case 'produce':
                        port.postMessage( { 'action' : 'consume', ... } );
                        break;
                }
            }
        }
        var action = e.data.action;
        switch(action) {
            case 'start':
                var arr = null; 
                //compute some data and fill the Array arr in order to send it by postMessage
                port.postMessage( { 'action' : 'consume', ... } );
                break;
            default:
        }
    }
    
  • The 2nd WebWorker consumer.js contains an event handler onMessage as well and it’s implemented in the almost same way as for the 1st one. The only difference is that the actions are switched (handled action is consume whereas the sent action is produce). This is because the WebWorkers tell to each other what the other one has to do.

    self.onmessage = function(e) {
        var port = e.ports[0];
        //register event handler on the received port to use the MessageChannel for the further communication
        if( !port.onmessage ) {
            port.onmessage = function(e) {  //event handler registration AGAIN!!!
                var action = e.data.action;
                //.... more code
                switch(action) {
                    case 'consume':
                        port.postMessage( { 'action' : 'produce', ... } );
                        break;
                }
            }
        }
        var action = e.data.action;
        switch(action) {
            case 'start':
                //... do nothing because producer.js must start with the work
                break;
            default:
        }
    }
    
  • Normally, at least one WebWorker should notify the main.js about the done work. Inside the WebWorker’s code this can be accomplished by this line of code:

                self.postMessage( ... someData ... );
    

    This is because we want to display the result of the communication by the (fantastic) jQuery queue implementation.

  • That’s all. If everything works and my heroku deployment is still alive you should be able to see something like this (numbers growing to the bottom and then being removed repeatedly):
    producer-consumer-webworker

    Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s