Continuo la saga degli articoli su HTML5 spiegando cos'è un Web Worker e mostrandone l'utilizzo.
L'esempio utilizzato in questo articolo,che puoi provare tu stesso al seguente link, consiste nel creare ed istanziare un web worker che, ad intervalli regolari, dopo aver incrementato la proprietà contatore (definita counter) di un oggetto con un valore variabile (definito step e appartenente all'oggetto stesso) invia alla pagina per la quale gira in background un messaggio per comunicare il nuovo valore di counter; la pagina, ricevuto il messaggio, scrive lo stato dell'oggetto ricevuto.
Per mostrare come è facile creare una comunicazione bidirezionale, è possibile modificare il valore della proprietà step intervenendo dalla pagina HTML in maniera che sia la pagina stessa ad inviare un messaggio al web worker, il quale al successivo incremento utilizzerà il nuovo valore di step.
Questo web worker si definisce dedicato perchè lavora esclusivamente per una sola pagina, con la quale nasce, ne condivide i dati e termina.
Cos'è un Web Worker?
Sostanzialmente come un thread, i web workers offrono la possibilità di eseguire scripts all'interno di una finestra di un browser senza bloccarne il caricamento e senza interferire con le attività dell'utente poichè i suddetti scripts girano in background.
Quando utilizzare un Web Worker?
Proprio per via della loro somiglianza con i threads, è consigliato delegare ai web workers l'esecuzione di scripts lunghi o onerosi in termini di risorse, che altrimenti costringerebbero ad una lunga attesa l'utente.
Scope & Security di un Web Worker
Dal punto di vista della sicurezza le API per l'utilizzo dei web workers sembrano avere le carte in regola.
La sicurezza in questo caso è garantita dalla scarsa area di validità (o scope) di cui gode il componente: questo infatti non ha accesso alla gran parte dei componenti ed oggetti javascript caricati nella pagina in cui gira e deve essere fornito tramite un file js esterno.
Un web worker può accedere ai seguenti oggetti e componenti:
- oggetto self: rappresenta il web worker stesso;
- oggetto navigator: rappresenta il browser; ma al worker è consentita la sola lettura delle seguenti proprietà:
- appName;
- appVersion;
- platform;
- userAgent;
- oggetto location: rappresenta la barra di navigazione della finestra; ma al worker è consentita la sola lettura delle seguenti proprietà:
- hash;
- host;
- hostname;
- href;
- pathname;
- port;
- protocol;
- search;
I web workers non hanno la possibilità di accedere e manipolare gli oggetti window e document quindi non possono utilizzare le API per la gestione del DOM.
Comunicare con un Web Worker
Dato che i web workers in alcun modo possono modificare il contenuto della pagina in cui girano bisogna capire come comunicare con il componente al fine di scambiarsi dati.
Con i web workers è possibile scambiare valori primitivi ed oggetti, ma non functions e ne metodi.
La comunicazione con il componente può essere sia mono che bidirezionale, ovvero può avvenire esclusivamente dal worker verso la pagina o viceversa (monodirezionale) oppure possono interagire flussi di dati provenienti da entrambe le sorgenti (bidirezionale).
Lo scambio di dati da e verso un web worker è basato sull'invio e la ricezione di messaggi.
E' più semplice comprendere il funzionamento osservando un esempio: di seguito è mostrato come inviare e ricevere dati facendo interagire una pagina e un web worker.
Il web worker, in background, ogni 2 secondi incrementa counter con il valore di step e tramite una chiamata al metodo postMessage() invia un messaggio con il nuovo valore incrementato alla pagina che lo mostra.
L'esempio permette di modificare il valore della proprietà step dalla pagina inviando al web worker il nuovo valore con cui incrementare counter.
//Codice HTML della pagina principale.
<!DOCTYPE html>
<html>
<head>
<title>Esempio di utilizzo di un Web Worker Dedicato</title>
<meta name="description" content="A smart and easy HTML5 Dedicated Web Worker." />
<meta name="author" content="Mirko Agrati" />
<script type="text/javascript">
/**
* Istanza dell'oggetto che viene scambiato
* con il web worker
*/
var token = null;
/**
* web worker: il costruttore riceve
* l'URI dello script che contiene la logica.
*/
var worker = new Worker("lib/dedicatedwebworker.js");
/**
* Implementazione del metodo onmessage(event):
* questo metodo viene richiamato ogni volta
* che il web worker riceve un messaggio.
*
* Ricevuto il messaggio con il nuovo
* valore incrementato esegue una chiamata
* alla funzione trace().
*/
worker.onmessage = function(event){
token = event.data;
trace();
}
/**
* In caso si manifesti un errore bloccante
* lo si può gestire con il metodo onerror(event).
*
* Segnalato l'errore interrompe il funzionamento
* del web worker.
*/
worker.onerror = function(event){
alert('ERRORE:\ndesc: ' + event.message
+ '\nfilename: ' + event.filename
+ '\nline number: ' + event.lineno);
stopWorker();
}
/**
* Interrompe l'esecuzione del web worker.
*/
function stopWorker(){
worker.terminate();
}
/**
* Traccia all'interno della pagina lo stato
* dell'ultima operazione di incremento
* eseguita del web worker.
*/
function trace(){
var text = 'counter = ' + token.counter
+ '; step = ' + token.step + '
';
document.getElementById("result").innerHTML += text;
}
/**
* Modifica il valore della proprietà step dell'oggetto token
* e richiama la funzione sendMessage() per comunicare al
* web worker che il valore incrementale è variato.
function updateStep(){
token.step = parseInt(document.getElementById('newstep').value);
sendMessage();
}
/**
* Invia un messaggio al web worker contenente
* l'istanza dell'oggetto token.
*/
function sendMessage(){
worker.postMessage(token);
}
</script></head>
<body>
<header>
<h1>HTML5 Dedicated Web Worker MA Test Page</h1></header>
<article id="content">
<input type="number" value="1" id="newstep">
<input type="button" value="cambia step" onclick="updateStep();">
<input type="button" value="Stop" onclick="stopWorker();">
<br>
<div id="result"></div></article>
<footer>
<p>© Copyright 2011 by Mirko Agrati</p></footer></body></html>
//Codice sorgente del Web Worker.
//File URI 'lib/dedicatedwebworker.js'
/**
* Classe che viene istanziata
* per l'invio e la ricezione
* dei dati alla pagina.
*
* Conserva lo stato dell'ultima
* operazione compiuta.
*/
function Token(){
this.counter = 0;
this.step = 1;
}
//Istanza di classe Token.
var token = new Token();
/**
* Ogni volta che il worker riceve un messaggio
* viene scatenata una chiamata a questo metodo.
*
* Aggiorna l'istanz di Token con quella ricevuta.
*/
onmessage = function(event){
token = event.data;
}
/**
* Incrementa il valore della proprietà
* counter e invia il nuovo valore alla pagina.
*/
function reply(){
token.counter += token.step;
postMessage(token);
}
/**
* Ogni 2 secondi esegue una chiamata
* alla funzione reply().
*/
setInterval(reply,2000);
Anche se il codice sorgente è ottimamente commentato :) è il caso di fare qualche chiarimento.
Prima di tutto, al caricamento della pagina viene creata un'istanza della classe Worker che, caricando lo script passato al costruttore, avvia il meccanismo eseguendo l'istruzione setInterval(reply,2000). Questa richiama ogni 2 secondi il metodo reply() del worker che incrementa il contatore e lo comunica alla pagina per tracciare l'operazione.
Quindi ogni 2 secondi la pagina principale riceve un messaggio dal worker con il nuovo valore incrementato di step unità. Ogni volta che questo si verifica viene eseguita una call al metodo onmessage(event) del worker che provvede a tracciare l'operazione eseguendo la funzione trace().
Qualora l'utente modificasse il valore della proprietà step dell'oggetto token tramite l'apposito FORM, si verificherebbe una chiamata alla funzione updateStep() che registrato il nuovo valore invierà un messaggio al worker per notificargli la variazione del valore di incremento.
Qui sono disponibili i sorgenti dell'esempio, in formato .zip .
Alla prossima,
MA