Java 5:o java.util.simultâneas.FutureTask - Semântica de cancelar() e done()
-
22-07-2019 - |
Pergunta
Atualmente, estou a caça de um bug desagradável em um ambiente multi-threaded usando FutureTasks e Executores.A idéia básica é que isso tem um número fixo de linhas de execução individual FutureTasks que calcular um resultado que está a ser exibido em uma tabela (não importa o GUI aspecto aqui).
Eu tenho estado a olhar para isso por tanto tempo, que eu estou começando a duvidar da minha sanidade.
Considere este trecho de código:
public class MyTask extends FutureTask<Result> {
private String cellId;
...
protected void done() {
if (isCancelled()) return;
try {
Result r = get(); // should not wait, because we are done
... // some processing with r
sendMessage(cellId, r);
} catch (ExecutionException e) { // thrown from get
...
} catch (InterruptedException e) { // thrown from get
...
}
}
...
}
Quando done()
é chamado por um Executor de manipulação de uma instância de MyTask, eu verificar se lá cheguei, porque a tarefa foi cancelada.Se for assim, eu ignorar todos os demais atividades, especialmente as que eu não chame sendMessage()
.
A documentação para FutureTask.feito() diz:
Protegido método invocado quando esta tarefa transições para o estado isDone (se normalmente ou através de cancelamento).A implementação padrão não faz nada.Subclasses pode substituir este método para invocar chamadas de retorno de conclusão ou executar a escrituração.Note que você pode consultar o status dentro da implementação deste método para determinar se esta tarefa foi cancelada.(Referência de API)
Mas o que eu não entendo da documentação de FutureTask
são a semântica enquanto done()
está sendo executado.Que se eu passar o isCancelled()
verifique no início, mas logo depois que alguma outra thread chama o meu cancel()
método?Será que fazer minha tarefa de mudar a sua mente e responder a isCancelled() == true
a partir de então?
Se sim, como seria mais tarde eu saber se a mensagem foi enviada?Olhando para isDone()
poderia apenas me dizer que a execução da tarefa foi concluída, mas como isCancelled()
eram verdadeiras, então, assim, eu não poderia saber se ele tem para enviar a mensagem em tempo.
Talvez isso é óbvio, mas eu realmente não vê-lo agora.
Solução
A partir da API (ênfase minha):
public boolean cancelar(boolean mayInterruptIfRunning)
Descrição copiados a partir da interface:Futuro
Tentativas para cancelar a execução desta tarefa. Essa tentativa falhará se a tarefa já concluída, que já foi cancelada, ou não pôde ser cancelada por algum outro motivo.
Assim FutureTask está trabalhando com o pressuposto de que você não pode cancelar uma tarefa quando ele foi transferido para o isDone fase.
Outras dicas
FutureTask#done()
não é mais do que uma vez para uma determinada instância chamada, e isso só é chamado por uma razão - run()
concluída com ou sem erro, ou cancel()
correu antes de qualquer um dos eventos anteriores ocorreram. O registro de conclusão por qualquer um desses resultados é travamento . A razão de um FutureTask
concluída não pode mudar, independentemente de eventos aparentemente acontecendo concorrentes "ao mesmo tempo."
Assim, dentro FutureTask#done()
apenas um dos isCancelled()
ou isDone()
retornará true, em seguida, e para sempre. É difícil distinguir entre isDone()
relatórios verdade por meio de erro ou conclusão bem-sucedida. Você não pode substituir set()
ou setException(Throwable)
decisivamente, tanto como delegado para o interior AQS
para decidir se o tentativa para gravar um sucesso produzindo de um valor ou encontrando uma exceção deve ficar. Substituindo o método só permite que você sabe que ele foi chamado, mas você não pode observar a decisão tomada pela implementação base. Se qualquer evento ocorrer "tarde demais" -Diga, após cancelamento-a tentativa de gravar o valor ou a exceção será ignorada.
Estudar a implementação, a única maneira que vejo para discernir um resultado não-canceladas sucesso de um erro é que morder a bala e chamada get()
.
Por que não enviar a mensagem "fora" da tarefa, com base no resultado da Future
Se você tomar essa abordagem, você deve usar dois separados ExecutorService casos: um para as tarefas principais e outra para as secundárias. Isso é para evitar impasses. Você não quer tarefas secundárias para iniciar e potencialmente bloquear tarefas principais de começar quando o tamanho do pool de threads é limitado.
Não há necessidade de estender FutureTask
Eu sugiro para escrever um caso de teste pequeno que lhe permite chamar cancel()
enquanto seus Future
exemplo trava em done()
e ver o que acontece.