Infra-estrutura Simultânea

Um dos maiores desafios de um sistema complexo é permanecer responsável enquanto as tarefas estão sendo executadas. Esse desafio é ainda maior em um sistema extensível, quando os componentes que não foram designados para execução conjunta estão compartilhando os mesmos recursos. O pacote org.eclipse.core.runtime.jobs enfrenta esse desafio, fornecendo infra-estrutura para o planejamento, a execução e o gerenciamento simultaneamente executando operações. Essa infra-estrutura tem como base a utilização de tarefas para representar uma unidade de trabalho que pode ser executada assíncronamente.

Tarefas

A classe de Tarefa representa uma unidade de trabalho assíncrona em execução simultânea com outras tarefas. Para executar uma tarefa, um plug-in cria uma tarefa e, em seguida, a planeja. Assim que uma tarefa é planejada, ela é incluída em uma fila de tarefas gerenciada pela plataforma. A plataforma utiliza um encadeamento de planejamento de segundo plano para gerenciar todas as tarefas pendentes. Quando uma execução de tarefa é concluída, ela é removida da fila e a plataforma decide qual tarefa deve ser executada a seguir. Quando uma tarefa fica ativa, a plataforma chama seu método de execução(). As tarefas são melhor demonstradas com um exemplo simples:
   class TrivialJob extends Job {
      public TrivialJob() {
         super("Trivial Job");
      }
      public IStatus run(IProgressMonitor monitor) {
         System.out.println("This is a job");
         return Status.OK_STATUS;
      }
   }
Uma tarefa é criada e planejada no seguinte fragmento:
   TrivialJob job = new TrivialJob();
   System.out.println("About to schedule a job");
   job.schedule();
   System.out.println("Finished scheduling a job");
A saída deste programa é dependente da sincronização. Ou seja, não há como ter certeza de quando o método de execução da tarefa será executado em relação ao encadeamento que criou a tarefa e a planejou. A saída será:
   Sobre o planejamento de uma tarefa
   Esta é uma tarefa
   Conclusão do planejamento de uma tarefa
ou:
   Sobre o planejamento de uma tarefa
   Conclusão do planejamento de uma tarefa
   Esta é uma tarefa

Se você deseja ter certeza de que uma tarefa foi concluída antes de continuar, você pode utilizar o método join(). Esse método bloqueará o responsável pela chamada até que a tarefa seja concluída ou até que o encadeamento de chamada seja interrompido. Vamos reescrever nosso fragmento de uma maneira mais determinista:

      TrivialJob job = new TrivialJob();
   System.out.println("About to schedule a job");
   job.schedule();
      job.join();
   if (job.getResult().isOk())
      System.out.println("Job completed with success");
    else
      System.out.println("Job did not complete successfully");
Supondo que a chamada join() não seja interrompida, esse método é garantido para retornar o seguinte resultado:
   Sobre o planejamento de uma tarefa
   Esta é uma tarefa
   Tarefa concluída com êxito

É claro que, normalmente, não é útil unir uma tarefa imediatamente depois de planejá-la, desde que você não tenha coincidência ao fazer isso. Neste caso você também pode fazer o trabalho a partir do método de execução da tarefa diretamente no encadeamento de chamada. Verificaremos alguns exemplos posteriormente em que a união faz mais sentido.

O último fragmento também faz uso do resultado da tarefa. O resultado é o objeto IStatus que é retornado do método de execução() da tarefa. É possível utilizar este resultado para transmitir os objetos necessários do método de execução da tarefa. O resultado também pode ser utilizado para indicar o defeito (retornando um IStatus com gravidade IStatus.ERROR) ou cancelamento (IStatus.CANCEL).

Operações Comuns da Tarefa

Estamos vendo como planejar uma tarefa e aguardar sua conclusão, mas há muitas outras coisas interessantes que você pode fazer com as tarefas. Se você planejar uma tarefa e depois decidir que não há mais necessidade, é possível parar a tarefa utilizando o método cancel(). Se a tarefa ainda não começou a ser executada quando cancelada, a tarefa é imediatamente descartada e não será executada. Se, por outro lado, a tarefa já começou a ser executada, depende da tarefa se ela deseja responder ao cancelamento. Quando você está tentando cancelar uma tarefa, aguardá-la utilizando o método join() torna-se cômodo. Aqui está uma linguagem comum para cancelar uma tarefa e aguardar até que a tarefa seja concluída antes de prosseguir:

      if (!job.cancel())
      job.join();

Se o cancelamento não entrar em efeito imediatamente, cancel() retornará false e o responsável pela chamada utilizará join() para aguardar o cancelamento bem-sucedido da tarefa.

Levemente menos drástico que o cancelamento é o método sleep(). Mais uma vez, se a tarefa ainda não estiver em execução, esse método fará com que a tarefa seja colocada em espera indefinidamente. A tarefa ainda será lembrada pela plataforma e uma chamada wakeUp() fará com que a tarefa seja incluída na fila de espera onde finalmente será executada.

Estados da Tarefa

Uma tarefa passa por vários estados durante o seu tempo de vida. Não só pode ser manipulada pela API como cancel() e sleep(), mas seu estado também é alterado na medida em que a plataforma é executada e conclui a tarefa. As tarefas podem passar pelos seguintes estados:

Uma tarefa pode ser colocada em espera somente se estiver atualmente WAITING. Ativar uma tarefa em espera a colocará de volta no estado WAITING. Cancelar uma tarefa a retornará para o estado NONE.

Se o plug-in precisa saber o estado de uma tarefa específica, pode registrar um listener de alteração de tarefa que seja notificado na medida em que a tarefa se move no ciclo de vida. Isso é útil para mostrar progresso ou, caso contrário, relatar em uma tarefa.

Listeners de Alteração de Tarefa

O método da Tarefa addJobChangeListener pode ser utilizado para registrar um listener em uma tarefa específica. O IJobChangeListener define o protocolo para responder às alterações de estado em uma tarefa:

Em todos esses casos, o listener é fornecido com um IJobChangeEvent que especifica a tarefa submetendo a alteração de estado e de status na conclusão (se estiver concluída).

Nota: As tarefas também definem o método getState() para obter (relativamente) o estado atual de uma tarefa. No entanto, esse resultado não é sempre confiável desde que as tarefas executadas em um encadeamento diferente e pode alterar o estado novamente no momento em que a chamada retornar. Os listeners de alteração da tarefa são o mecanismo recomendado para descobrir as alterações de estado em uma tarefa.

O Gerenciador de Tarefas

O IJobManager define o protocolo para trabalhar com todas as tarefas no sistema. Os plug-ins que mostram progresso ou, caso contrário, funcionam com a infra-estrutura da tarefa podem utilizar oIJobManager para executar tarefas como suspender todas as tarefas no sistema, localizando qual tarefa está em execução ou recebendo feedback de progresso sobre uma tarefa específica. O gerenciador de tarefa da plataforma pode ser obtido utilizando a API da Plataforma:

      IJobManager jobMan = Platform.getJobManager();

Os plug-ins interessados no estado de todas as tarefas no sistema podem registrar um listener de alteração de tarefa no gerenciador de tarefas em vez de registrar os listeners em várias tarefas individuais.

Famílias da Tarefa

Às vezes, é mais fácil para um plug-in trabalhar com um grupo de tarefas relacionadas como uma unidade única. Isso pode ser executado utilizando as famílias da tarefa. Uma tarefa declara que pertence a uma determinada família, substituindo o método belongsTo:

   public static final String MY_FAMILY = "myJobFamily";
   ...
   class FamilyJob extends Job {
      ...
      public boolean belongsTo(Object family) {
         return family == MY_FAMILY;
      }
   }
O protocolo de IJobManager pode ser utilizado para cancelar, unir, colocar em espera ou localizar todas as tarefas em uma família:
   IJobManager jobMan = Platform.getJobManager();
   jobMan.cancel(MY_FAMILY);
   jobMan.join(MY_FAMILY, null);

Já que as famílias de tarefas são representadas utilizando objetos arbitrários, é possível armazenar estados interessantes na família da tarefa em si e as tarefas podem construir dinamicamente os objetos da família conforme necessário. É importante utilizar objetos da família que são exclusivos para evitar interação acidental com as famílias criadas por outros plug-ins.

As famílias também são um modo conveniente de localizar grupos de tarefas. O método IJobManager.find(Família do objeto) pode ser utilizado para localizar instâncias de todas as tarefas em execução, aguardando e em espera a qualquer momento.