Ejemplo de Web Workers, los hilos javascript

Desde que los navegadores tenían soporte para Javascript, su código siempre se ha ejecutado en un único hilo. Los scripts que se desarrollan para el navegador están orientados a eventos y estos se van ejecutando en una especie de cola. Es decir, conforme se van generando los eventos estos van a una cola de tareas y Javascript los va procesando de uno en uno en un bucle.

Web Workers, hilos en tu navegador

HTML5 trae como novedad los Web Workers, que son procesos de javascript separados. Por lo tanto cada Worker tiene su propio bucle de procesamiento de tareas/eventos. El Worker se inicia desde el hilo principal del navegador y puede comunicarse con este y otros Workers mediante el envío de mensajes.

¿Son realmente necesarios? Bueno, hasta ahora con el asincronismo rampante como por ejemplo el de Ajax nos ha ido bien y ya llevamos unos años moviendo datos y soportando eventos. Peeero HTML5 viene con una serie de novedades que pueden agradecer ese tipo de mecanismos: por ejemplo el canvas (canvas es un lienzo), ese panel donde podemos pintar gráficos, la geolocalización, o las propias bases de datos indexedDB pueden beneficiarse de los hilos.

Limitaciones

Los Web Workers tienen su propio hilo a su disposición pero curiosamente tienen limitaciones muy considerables. ¿Te puedes creer que no puedes hacer un alert desde un worker? Tal cual, no tienes acceso al objeto predefinido window y a algunos otros solo puedes acceder en modo solo-lectura. Estas serían algunas de las limitaciones más importantes (a la velocidad que se transforma la web puede que todo esto vaya cambiando, revisa la fecha estelar de este post):

  • Solo pueden comunicarse mediante mensajes: no existen estados compartidos ni variables/estructuras compartidas.
  • Son pesados: no están pensados para levantar 100 workers, se habla de 10 como mucho.
  • No se pueden depurar ni testear: lo que ocurre en el Worker es una caja negra a día de hoy
  • Solo tipos simples en contenido de mensajes: esto puede variar pero no todos los navegadores soportan tipos complejos así que debes usar int, String,... pero ahora es cuando te ajustas tus gafas de pasta y piensas: mmmm JSON.stringify y serializo lo que quiera.
  • Sin acceso a console: al menos lo que he podido comprobar con mis medios
  • Sin acceso al DOM: olvídate de getElementById(), podrás usar un jquery limitado
  • Sin acceso a window: olvídate de los dialogs tipo alert, confirm,...
  • No hay localStorage ni sessionStorage: aunque sí tendrás acceso a IndexedDB

Según se mire, esas limitaciones pueden tener sus ventajas ya que en cierto modo orientan a los Workers a hacer un tipo de tareas no vínculadas al interfaz sino centradas en una tarea concreta. Además el hecho de que no haya estado compartido nos quita de un plumazo todas la necesidades de sincronización y los problemas de bloqueo commo el deadlock. También abre un inmenso campo abonado para que la gente proporcione sus propios mecanismos de sincronización.

Para poder contar con alguna ayuda, tenemos por ejemplo Jquery Hive que aparte de interfaces para crear Workers también nos facilita un subconjunto de jquery en el worker ($.get() , $.post() ,...)

Un ejemplo simple de Web Worker

Vamos a ver cómo crear un de Worker. Es tan simple como instanciar un objeto de la clase Worker pasándole como parámetro un fichero de código .js. Esto se haría en la página html (la que contiene el hilo principal); creamos la instancia, mandamos mensajes y establecemos un callback para procesar los mensajes del Worker.



            // This is how we create a Worker instance
			var worker = new Worker('helloWorker.js');

            // We send a message to worker
            console.log('Sending a message to Worker from main thread:');
			worker.postMessage('World' );

            // We set a callback for every message received from Worker
            worker.onmessage = function (event){
                // event.target: this is the worker object
                console.log("Sender object: " + event.target);
                // event.data: the message content
                console.log("Received message: " + event.data);
            };
    

Y este sería el contenido del Worker, del fichero helloWorker.js

/**
* helloWorker.js
* a simple worker sending/receiving messages 
* @author Pello Xabier
*/

var workerName = 'Anhell';

self.postMessage('Hello');
self.postMessage('I am ' + workerName +' the Worker');

// We set this callback to receive messages from main thread
self.onmessage = function (event) {
	self.postMessage('Worker thread> Message received: ' + event.data);
};

Así lo vemos en la consola web de un firefox en linux:

Logs de ejemplo simple de web workers
No todo son limitaciones

Bueno, al menos algunas cosillas sí que se pueden hacer oficialmente desde un Worker. Se supone, y digo supone porque con los navegadores nunca se sabe, que en un Worker disponemos de:

  • .self: como se ve en el ejemplo anterior, es una referencia a sí mismo en el Worker
  • .close(): un interesante método para terminar el worker.
  • importScripts('js/miscript.js'): un método para cargar scripts, puede resultar muy útil pero ojo, si cargas jquery te fallará porque jquery hacce referencia a window.
  • Objetos predefinidos navigator y location: en modo solo lectura
  • XMLHttpRequest: para ajax a pelo, pero teniendo el jQuery Hive igual no compensa.
  • setTimeout y setInterval: lás míticas funciones para ejecutar cosas en un determinado tiempo
Las dos últimas son muy interesantes si vamos a trabajar con Workers. Para refrescar tu memoria, setInterval(func,miliseg) nos permite ejecutar una función una y otra vez con determinados intervalos de tiempo mientras que setTimeout(func,miliseg) ejecuta una función una vez pasado un determinado tiempo.

Un par de Workers

Vamos a ver un par de Workers compitiendo, Se trata de una Worker llamado Spaceship que va avanzando posiciones en un intervalo de tiempo. Para que no vaya a saco se usa la función setTimeout. En la página se van actualizando dos barras de progreso conforme los SpaceShips avanzan. Si todo va bien igual puedes probarlo en este propio post.

/**
* Web worker that simulates a spacheShip flying
* It sends a message to main thread to notify the distance travelled
* spacheShip.js
* @author Pello Xabier Altadill Izura
* @greetz Han Solo fans
*/

var distance = 0;
var total = 400;
var randomDistance = 0;
var speed = 10;
var ship = "spaceship1";

importScripts('js/randomLib.js');

/**
* sets space ship name
*/
function setSpaceship (name) {
	ship = name;
}

/**
* movesSpaceship in intervals
* untils distance is 0 or negative
*/
function moveSpaceship () {
		setTimeout("move()",100);
}

/**
* moves the spaceship and notifies the main thread
*/
function move () {
	randomDistance = -1;
	
	if (distance < total) {
		randomDistance = random(speed);
		// We send a message informing about ship name and distance moved and the remaining distance
		self.postMessage(ship + ":" + randomDistance+":"+distance);
		distance += randomDistance;
		moveSpaceship();
	}
	
}

// We set this callback to receive messages from main thread
// If we receive move now, the movement begins
self.onmessage = function (event) {
	self.postMessage('Spaceship thread> Message received: ' + event.data);
	if (event.data == "move")
		moveSpaceship();
};

Bien, el código es bastante mejorable y me gustaría poder darle más forma de POO pero bueno, es una primera aproximación. Si no te funciona pues al menos puedes ver el código fuente de este post.

WebWorkers sample
Spaceship1

Spaceship2

by Pello Altadill 08/20/2013 12:51:28 - 1390 hits

Services en Android

En Android no solamente hay aplicaciones de ventanas o activities. Hay muchos otros tipos de elementos como por ejemplo los Services. Los servicios nos permiten dejar un programa residente en la memoria y disponible para cualquiera que lo necesite. Los servicios no tienen interfaz gráfico ni nada, son programas pensados para ser utilizados desde las Activities o incluso desde otros servicios. Un service puede crearse para dar servicio -privado- a una sola aplicación o puede hacerse público (a través ...

by Pello Altadill 08/19/2013 22:29:37 - 1267 hits

More »

Creando módulos en Node.js

Lo del servidor web con pocas líneas o refactorizado para que tenga forma de clase va quedando más o menos apañado. Pero en cualquier proyecto que vaya creciendo vamos a necesitar bastantes más cosas y claro, ir metiendo todo el programa en un único fichero, aunque algún programador bajeril pueda decir que es más simple, pues está muy feo. Como en cualquier otro lenguaje y sea el tipo de programación que se...

by Pello Altadill 08/18/2013 22:45:09 - 497 hits

More »

AsyncTask en Android

Los AsyncTask están recomendados para tareas concretas y finitas, como por ejemplo descargar el contenido de una URL. Si lo que necesitas es una especie de Hilo que esté ejecutándose contínuamente tendrías que usar los Thread convencionales (no se detienen ni al pulsar home ni al pulsar Back) o directamente crear un Service de Android. Para crear un AsyncTask debes extender la clase AsyncTask indicándole tres tipos de clase en la declaración, como por ejemplo AsyncTask<String, String, V...

by Pello Altadill 08/18/2013 11:06:08 - 1063 hits

More »

Perfiles de desarrollo y producción con Maven

Algo muy común y recomendable en cualquier proyecto medianamente serio es contar con un servidor de desarrollo y otro de producción, además del propio equipo del programador donde se hacen las primeras pruebas. Los programas se prueban en local, se prueban en desarrollo y finalmente, cuando se ha verificado que todo está correcto, se puede pasar a producción donde se explotará una BD con datos reales y habrá usuarios reales. Hay lugares en los que incluso existe una servidor de pre-producción....

by Pello Altadill 08/17/2013 22:16:00 - 1040 hits

More »

Hilos en Android con Threads java

Toda aplicación de Android tiene al menos un hilo o thread de ejecución. Ese thread es el encargado de la pantalla que tienes ante ti y es imprescindible por la manera en la que Android gestiona los cambios en el interfaz: a través de una cola de mensajes. Es decir, cada vez que aplicamos algún cambio en algún View no se aplica directamente sino que se deja la petición en una cola. Y ese hilo, el UI thread o el hilo principal de la aplicación es quien precisamente se encarga de procesar esa cola...

by Pello Altadill 08/17/2013 10:50:52 - 1071 hits

More »

Servidor web de Node.js refactorizado

Sí, seguro que el primer código que has visto de Node.js es uno de esos milagrosos servidores web hechos con pocas líneas. Olvídese de aparatosos sockets, de punteros o de BufferedStreamsWriters, un módulo, unos callbacks, eliges un puerto y a dar servicio. Bueno, uno trata de ser un good Node.js citizen y este código se entiende y mola, pero a la larga, si lo queremos ir mejorando y añadiendo otras funcionalidades cada vez va a ser más complicado mantenerlo. Lo que propongo en ...

by Pello Altadill 08/16/2013 23:18:31 - 532 hits

More »

Hibernate self-join con anotaciones

Vamos a dejar por unos instantes tanto Javascript sin tipos y sin estructura y pongámonos otra vez la corbata para volver al mainstream de aplicaciones empresariales J2EE. Quedaba pendiente ver cómo montar un self-join pero con anotaciones, es decir una clase que se hace referencia a sí misma. En su momento ya puse aquí la versión con el mapeo por XML, vamos a ver cómo quedaría la cosa pero usando las anotaciones en la ...

by Pello Altadill 08/16/2013 17:06:58 - 363 hits

More »

Un vistazo a Backbone

Backbone es un framework MVC de javascript para clientes web que facilita el desarrollo de aplicaciones de una sola página. El hecho de aplicar MVC permite separar los datos de la presentación y Backbone lo hace sin invadir la página y con una serie de funciones que permiten la persistencia de datos en el servidor. Dicho de otro modo, podemos hacer páginas web que man...

by Pello Altadill 08/16/2013 01:37:50 - 521 hits

More »