Início > programação, programação JSF > Como Executar Tarefas em Paralelo no JSF – Abordagem com Threads

Como Executar Tarefas em Paralelo no JSF – Abordagem com Threads

Essa semana me deparei com um problema em um sistema desenvolvido em JSF aqui da UECE. Após o usuário clicar em um CommandButton para executar uma action, o sistema tinha que atualizar uma entidade no banco de dados e logo depois disparar uma boa quantidade de e-mails informativos – não precisam de confirmação se foram enviados com sucesso – diferentes (variando de 4 a mais de 100) e para vários destinatários. O detalhe era: como fazer para não deixar o usuário esperando o envio de mais de 100 mensagens? Qual a solução mais prática?

Algumas pessoas me sugeriram usar o Quartz com uma tarefa recorrente que ficaria verificando se havia mensagens para enviar em uma fila, mas seria mais um componente a ser colocado no sistema e nem todos os desenvolvedores do departamento saberiam usá-lo.

A solução muito simples que encontrei foi criar uma Thread separada, responsável por realizar a tarefa de enviar as mensagens. Assim, a execução do método no Managed Bean continuaria imediatamente após o disparo da Thread, independente de as mensagens terem sido enviadas ou não. Vamos aos códigos:

Método do Managed Bean

/**
* Método que finaliza a inserção de trabalhos na sessão
* @return Navigation Rule para a mesma página
*/
public String fecharSessao(){
   try {
      if (trabalhos.size() > 0) {
         sessao.setFechada(true);
         new SessaoBO().update(sessao);

         // envia e-mails em thread separada para dar resposta mais rápida para o usuário
         EnviaEmailTrabalhosAlocadosThread em = new EnviaEmailTrabalhosAlocadosThread(trabalhos).start();

         /*
          * Com o disparo da thread, esse método é executado logo em seguida, sem que o envio das
          * mensagens tenha que ocorrer ou ser completado
         */
         adicionaMensagemDeSucesso("sucesso.finalizar.sessao");

      } else {
         adicionaMensagemDeErro("trabalho.alocado.empty");
      }
   } catch (ViolacaoChaveHibernateException e) {
         adicionaMensagemDeErro("erro.editar.entidade.exception", "sessão");
   } catch (ObjetoNaoEncontradoHibernateException e) {
         adicionaMensagemDeErro("erro.editar.entidade.nao.encontrada.exception", "sessão");
   }

   return null; // mostra a mesma página
}

Thread para Envio das Mensagens

package br.uece.semana.disparador;

import java.util.List;
import br.uece.semana.model.Trabalho;

public class EnviaEmailTrabalhosAlocadosThread extends Thread {

   private List<Trabalho> trabalhos;

   public EnviaEmailTrabalhosAlocadosThread(List<Trabalho> trabalhos) {
      this.trabalhos = trabalhos;
   }

   @Override
   public void run() {
      DisparadorDeEmail disparador = new DisparadorDeEmail();

      for (int i=0; i<trabalhos.size(); i++) {
disparador.enviaMensagem(trabalhos.get(i).getEstudante().getNomeEstudante(), 						trabalhos.get(i).getEstudante().getEmail(), trabalhos.get(i).getTitulo());
      }
   disparador = null;
   trabalhos = null;
   }
}

Observem que no Managed Bean, logo após a Thread ser disparada, a execução do método continua e a mensagem de sucesso é enviada imediatamente ao usuário, não causando atraso nenhum no retorno do processo.

Finalmente há um detalhe nessa abordagem: ela só pode ser utilizada em procedimentos que não precisam de confirmação do status da mensagem. Por exemplo: uma funcionalidade Fale Conosco precisa dar retorno ao usuário se a mensagem foi enviada ou não para o destinatário, e neste caso não teríamos como fazer isto.

Anúncios
  1. 30/11/2010 às 22:51

    boa dica pablo! realmente util!

  2. 01/04/2011 às 01:29

    Pablo,

    Mas se eu quiser criar uma tarefa que verifique todos os dias o meu banco em busca de uma mudança específica e caso encontre esta mudança dispare uma email. Eu teria que utilizar o Quartz?

  3. Heitor
    29/05/2012 às 17:34

    Boa tarde Pablo,

    Adotei a mesma abordagem em um sistema aqui do trabalho. O único problema que vejo é que o email pode não ser enviado se alguma exceção ocorrer no momento do envio.

    • 30/05/2012 às 08:44

      Certamente, Heitor. Mas esse tipo de abordagem deve ser usado quando o usuário não precisa saber se o e-mail foi enviado ou não, ou seja, o disparo da mensagem é apenas uma tarefa secundária. Se você quer saber se ocorreu ou não alguma exceção no envio da mensagem, basta usar alguma rotina de log, que inclusive pode enviar notificações a interessados, quando erros acontecem. Uma dessa soluções é o commons logging, da Apache.

  4. Camillo Targas
    05/08/2012 às 14:45

    Muito bom, estou utilizando com sucesso!

  5. 20/08/2012 às 16:02

    Olá Pablo,
    Eu tenho uma dúvida. Eu tenho uma aplicação em JSF aplicando MVC. Nesta aplicação eu tenho, dentro dos DAOS, métodos que realizam consultas SQLs que demoram um pouco e nisto, eu gostaria que a cada ‘onclick’ fosse gerado um modalPanel com um componente rich:progressBar. Pensei em utilizar a thread mas aí fiquei em dúvida se utilizo thread para abrir o modal ou para executar a tarefa. Bom, cheguei a um consenso que teria que utilizar Thread no DAO. Mas como definir qual método do DAO a Thread irá acessar? Eu consigo inserir métodos dentro do run()? Poderia me dar uma luz na implementação desta ideia?

    • 20/08/2012 às 19:13

      Max,

      Não é adequado você colocar Thread dentro do DAO. Fazendo isso você terá uma violação de coesão (ou responsabilidade), pois um DAO é um objeto para acesso ao banco e não para tratar de lógica de visão.

      No caso, você deve usar o onclick do botão para mostrar o progressBar enquanto a requisição é processada. Após o termino (evento oncomplete, por exemplo) você oculta a progressBar e mostra o modalPanel. Me diz qual versão do JSF e qual framework RIA (Richfaces, Primefaces, Icefaces) e a versão tb que você está usando que posso te ajudar. Vou mandar essa mensagem em PVT e aguardar a resposta.

      Abraço,

      Pablo.

  6. 13/03/2013 às 07:24

    Tive um problema semelhante pra resolver na Fundação que trabalhava (Funcap) ,como trabalhava com ajax fiz um Kron e uma Pilha em Javascript(Rodando no Servidor),o Kron fazia requisições ajax a uma entidade e verificava a existencia de emails a serem enviados em determinado dia,hora,minuto e caso os encontrasse populava uma pilha que os enviava com um certo delay para evitar span…. abaixo segue o código, poderia ser adaptado para o JSF.

    var Pilha = {
    “agendamento” : [],
    “process” : function() {
    if(Pilha.tamanho() > 0){
    Pilha.retira();
    }
    },
    “id” : 0,
    “start” : function() {
    Pilha.stop();
    Pilha.id = setInterval(“Pilha.process()”,5000);
    },
    “stop” : function() {
    clearInterval(Pilha.id);
    },
    “esvazia” : function(){
    Pilha.agendamento.clear();
    },
    “insere” : function(elemento){
    Pilha.agendamento.push(elemento);
    $(‘pilha’).innerHTML = ““+Pilha.tamanho()+”“;
    },
    “retira” : function(){
    var elemento = Pilha.agendamento.pop();
    $(‘pilha’).innerHTML = ““+Pilha.tamanho()+”“;
    var url = ‘executaAgendamento.php’;
    var parametros = ‘id=’+elemento;
    var myAjax = new Ajax.Request( url, { method: ‘post’, parameters: parametros, onLoading: carregando, onComplete: informaExecucaoAgenda });
    },
    “tamanho” : function(){
    return Pilha.agendamento.length;
    }
    }

    var Cron = {
    “jobs” : [],
    “process” : function() {

    if(Cron.tamanho() > 0){

    $(‘log’).innerHTML = “Kron startado!”;
    $(‘log’).innerHTML += “Pilha de agendamentos criada!”;
    $(‘kron’).innerHTML = ““+Cron.tamanho()+”“;
    var url = ‘tempoServidor.php’;
    var parametros = ‘id=0’;
    var myAjax = new Ajax.Request( url, { method: ‘post’, parameters: parametros, onLoading: carregando, onComplete: Cron.check });
    }else{
    $(‘log’).innerHTML = “Kron startado!”;
    $(‘log’).innerHTML += “Pilha de agendamentos criada!”;
    $(‘log’).innerHTML += “Nenhum Trabalho agendado!”;
    Cron.atualizaTrabalho();
    }

    },
    “check” : function(request) {

    var now = request.responseText.split(‘/’);

    for (var i=0; i<Cron.jobs.length; i++) {

    if ( Cron.jobs[i].minute == "*" || parseInt(Cron.jobs[i].minute, 10) == parseInt(now[0],10)){
    if ( Cron.jobs[i].hour == "*" || parseInt(Cron.jobs[i].hour, 10) == parseInt(now[1],10)){
    if ( Cron.jobs[i].date == "*" || parseInt(Cron.jobs[i].date, 10) == parseInt(now[2],10)){
    if ( Cron.jobs[i].month == "*" || parseInt(Cron.jobs[i].month, 10) == parseInt(now[3],10)){
    if ( Cron.jobs[i].year == "*" || parseInt(Cron.jobs[i].year, 10) == parseInt(now[4],10)){
    if ( Cron.jobs[i].day == "*" || parseInt(Cron.jobs[i].day, 10) == parseInt(now[5],10)){
    Cron.jobs[i].run();

    }
    }
    }
    }
    }
    }
    }
    Cron.esvazia();
    Cron.atualizaTrabalho();
    },
    "atualizaTrabalho" : function(){
    $('kron').innerHTML = "“+Cron.tamanho()+”“;
    var url = ‘recarregaJobs.php’;
    var parametros = ‘id=0’;
    var myAjax = new Ajax.Request( url, { method: ‘post’, parameters: parametros, onLoading: carregando, onComplete: Cron.recarregaJobs });

    },
    “recarregaJobs” : function(request){

    var minuto = “”;
    var hora = “”;
    var dia = “”;
    var mes = “”;
    var ano = “”;
    var idAgenda = “”;
    var novoTrabalho = “”;
    var agenda = “”;

    var agendamentos = request.responseText.split(‘|’);

    for (var z=0; z<agendamentos.length – 1; z++) {

    agenda = agendamentos[z].split('-');

    minuto = parseInt(agenda[0],10);
    hora = parseInt(agenda[1],10);
    dia = parseInt(agenda[2],10);
    mes = parseInt(agenda[3],10);
    ano = parseInt(agenda[4],10);
    idAgenda = agenda[5];
    novoTrabalho = minuto+' '+hora+' '+dia+' '+mes+' * '+ano;

    if(parseInt(agenda[6],10)==1){
    insereTrabalho(novoTrabalho,idAgenda);
    }else{
    new Pilha.insere(idAgenda);
    }

    minuto = null;
    hora = null;
    dia = null;
    mes = null;
    ano = null;
    iidAgenda = null;
    novoTrabalho = null;
    agenda = null;

    }
    },
    "id" : 0,
    "start" : function() {
    Cron.stop();
    Cron.id = setInterval("Cron.process()",60000);
    },
    "stop" : function() {
    clearInterval(Cron.id);
    },
    "esvazia" : function(){
    Cron.jobs.clear();
    },
    "Job" : function(cronstring, fun) {

    var _Job = this;
    var items = cronstring.match(/^([0-9]+|\*{1})[ \n\t\b]+([0-9]+|\*{1})[ \n\t\b]+([0-9]+|\*{1})[ \n\t\b]+([0-9]+|\*{1})[ \n\t\b]+([0-9]+|\*{1})[ \n\t\b]+([0-9]+|\*{1})[ \n\t\b]*$/);
    _Job.minute = items[1];
    _Job.hour = items[2];
    _Job.date = items[3];
    _Job.month = items[4];
    _Job.day = items[5];
    _Job.year = items[6];
    _Job.run = fun;
    Cron.jobs.push(_Job);
    _Job = null;
    items = null;
    },
    "tamanho" : function(){
    return Cron.jobs.length;
    }
    }

    Cron.start();
    Pilha.start();

  1. 07/12/2011 às 16:39

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

w

Conectando a %s

%d blogueiros gostam disto: