Desenvolvimento

31 out, 2011

Web Workers: seu processador trabalhando para o HTML5

Publicidade

Cada vez mais é
necessária a criação de documentos HTML que ofereçam mais
recursos e interfaces mais responsivas.

Com o HTML5, é
possível criar experiências maravilhosas com a conveniência de
recursos que antes eram encontrados apenas em plataformas ricas para
Desktop ou plug-ins como Flash e Silverlight.

A quantidade de
novos recursos disponíveis é muito grande, e a necessidade de
utilizar muitos desses recursos simultaneamente também é crescente.

E, como todos
sabemos, quanto mais scripts sendo executados “ao mesmo tempo”,
maior é a carga de processamento para a Thread de UI, na qual acontecem
as atualizações da interface com o usuário. Mesmo com os esforços
para a melhoria no modelo de execução de scripts nos navegadores
mais modernos, em algum momento, fatalmente esbarraremos nos limites
de processamento físicos da Thread principal, seja em falta de
resposta ou na interrupção de scripts de longa duração por
interações do usuário.

Para evitar esse
tipo de situação, existe uma especificação nova que acrescenta
bastante poder ao HTML5: Web Workers.

Com Web Workers, é
possível executar scripts em threads distintas da thread de UI, o
que permite muito mais eficiência na execução e fluidez na
interface, principalmente por se tratar de um modelo que utiliza os
núcleos (cores) do processador disponível.

Até então, esse
tipo de execução só era acessível para plataformas não HTML
através de APIs não tão amigáveis para o desenvolvedor.

Uma
das características mais interessantes das especificações do W3C é
a simplicidade de utilização dos recursos, e isso se aplica a Web
Workers, o que torna o trabalho muito simples para o desenvolvedor.

A maneira mais
simples para se utilizar um WebWorker é utilizar um arquivo de
script separado. Poderia também ser utilizado um script inline e
outras modalidades como DedicatedWorkers
e SharedWorkers, porém, para este
artigo de introdução, vamos focar na maneira mais prática.

Declarando o
Worker na thread da UI:

Default.htm

var worker = new Worker("myscript.js");

Isso faz com que o
Worker instancie o script para atuar como o Worker em background.
É
importante adicionar eventhandlers aos eventos do Workerna thread de
UI, onmessage e onerror

worker.onmessage = onWorkerMessage;
worker.onerror = onWorkerError;

Abaixo, a
implementação das duas funções que vão ser executadas quando os
eventos forem disparados:

functiononWorkerMessage(e) 
{
label.innerHTML = e.data;
}

functiononWorkerError(e)
{
// Faça algo quando ocorrer um erro
}

Agora que o Worker
já está pronto, é necessário utilizar o método postMessage()
para que ele comece a ser executado:

worker.postMessage("start");

É possível enviar
objetos mais complexos via postMessage e utilizar esses objetos para
uma comunicação mais precisa entre as Threads UI e o Background.

Também é
importante adicionar o eventhandler de mensagens ao script que será
executado como o Worker para efetuar a troca de
mensagens/comandos.

myscript.js

self.onmessage = onWorkerMessage;

functiononWorkerMessage(e)
{
switch(e.data)
{
case "start":
self.postMessage("Iniciando...");
break;
case "stop":
self.postMessage("Parando...");
self.close();
break;
}
}

Assim que o Worker
receber a primeira mensagem “start”, ele retornará uma mensagem
para a thread UI que exibirá essa mensagem para o usuário.

Mas, além de
tratarmos as mensagens recebidas pelo Worker, também devemos
implementar a lógica que será executada em background.

Neste
caso, eu escolhi utilizar um loop infinito que incrementa uma
variável para que a visualização seja clara do exemplo
funcionando.

var value = 0;

while (true)
{
self.postMessage("Valor atual: " + value++);
}

Basicamente, esse
loop avisa à thread de UI o valor atual à cada atualização e que,
por sua vez, exibe o valor para o usuário.

Existem duas formas
de parar a execução de um Workers. Podemos utilizar o método
terminate() na thread da UI ou o
método close() na thread
em background.

Neste caso, podemos
utilizar a interface para enviar mensagens para parar o processamento
do worker e então terminá-lo:

<buttononclick="stop();">Parar Worker</button>

function stop()
{
worker.postMessage("stop");
}

A mensagem “stop”
já foi tratada pelo script do nosso Worker customizado, porém como
tudo acontece muito rápido, as mensagens não chegam a ser exibidas
devido ao fato de o Worker já ter sido fechado. A dica aqui é
utilizar mais mensagens e só terminar o Worker ao final de todo o
processamento necessário.

Abaixo o código
completo da página HTML e do Script:

Default.htm

<!doctypehtml>
<html>
<head>
<title>Web Worker</title>

<script>

var worker;
var label;

functioninit()
{
label = document.getElementById("label");

worker = new Worker("myscript.js");

worker.onmessage = onWorkerMessage;
worker.onerror = onWorkerError;

worker.postMessage("start");
}

functiononWorkerMessage(e)
{
label.innerHTML = e.data;
}

functiononWorkerError(e)
{
// Faça algo quando ocorrer um erro
}

function stop()
{
worker.postMessage("stop");
}

</script>

</head>
<bodyonload="init();">
<divid="label"></div>
<buttononclick="stop();">Parar Worker</button>
</body>
</html>

myscript.js

self.onmessage = onWorkerMessage;

functiononWorkerMessage(e)
{
switch(e.data)
{
case "start":
self.postMessage("Iniciando...");
break;
case "stop":
self.postMessage("Parando...");
self.close();
break;
}
}

var value = 0;

while(true)
{
self.postMessage("Valoratual: " + value++);
}

Vejam abaixo a
variação de utilização do processamento da minha máquina (que
possui quatro núcleos) ao utilizar o Web Worker.

Antes de acionar a
página do Worker:

Durante o
processamento do Woker:

Como vocês puderam
notar, a API de Workers define automaticamente a afinidade entre
threads e núcleos do meu processador e garante um processamento
muito rápido sem interferir na thread de UI.

Vale a pena conferir
este comparativo entre o processamento de um script no modelo baseado
na thread de UI e utilizando o Workers que é baseado no
test262
do Ecmascript.

Essas
implementações funcionam nas versões mais atuais dos navegadores.
Por se tratar de um Draft de especificação e implementação por
parte dos navegadores, você encontrará diferença na performance e
falhas na execução dos testes, mas é perfeitamente viável para
iniciar o desenvolvimento.

O código deste
artigo é baseado na especificação do W3C
e
foi testado com o Internet Explorer 10 Platform Preview, que pode ser
instalado a partir deste link

***

Veja mais detalhes no HTML5 & JavaScript Center

***

Autores

Murilo Maciel Curti (@shinjiR) é membro do time de especialistas em desenvolvimento da Microsoft Brasil com foco em desenvolvimento para Windows, HTML5, Windows Phone e jogos.
Diego Blanco (@diblanco_) é bacharel em Sistemas da
Informação, sempre trabalhou com Web e é apaixonado por isso. Tem
basicamente dois hobbies, tocar guitarra e estar antenado com as
últimas dos Games. Atualmente faz parte da equipe de Audiências
Técnicas da Microsoft.
www.diblanco.net