Execute o AsyncTask várias vezes


127

Na minha Atividade, uso uma classe que se estende de AsyncTask e um parâmetro que é uma instância desse AsyncTask. Quando ligo mInstanceOfAT.execute("")está tudo bem. Mas o aplicativo falha quando pressiono um botão de atualização que chama novamente o AsyncTask (caso o trabalho de rede não funcione). A causa aparece então uma exceção que diz

Não é possível executar a tarefa: a tarefa já foi executada (uma tarefa pode ser executada apenas uma vez)

Tentei chamar cancel (true) para a instância do Asyctask, mas também não funciona. Até agora, a única solução é criar novas instâncias do Asyntask. Essa é a maneira correta?

Obrigado.

Respostas:


217

AsyncTask instâncias podem ser usadas apenas uma vez.

Em vez disso, basta chamar sua tarefa como new MyAsyncTask().execute("");

Nos documentos da API do AsyncTask:

Regras de encadeamento

Existem algumas regras de encadeamento que devem ser seguidas para que esta classe funcione corretamente:

  • A instância da tarefa deve ser criada no encadeamento da interface do usuário.
  • execute (Params ...) deve ser chamado no thread da interface do usuário.
  • Não chame onPreExecute (), onPostExecute (Result), doInBackground (Params ...), onProgressUpdate (Progress ...) manualmente.
  • A tarefa pode ser executada apenas uma vez (uma exceção será lançada se uma segunda execução for tentada.)

2
Que o que eu disse que fiz foi essa a única possibilidade? Porque eu quero salvar a memória, em vez de criar um novo objeto.
21411 Dayerman


@StevePrentice: se eu criar uma instância da tarefa a cada x segundos com a nova task (). Execute (param), para enviar dados para um servidor, como o coletor de lixo poderá liberar memória quando a execução for concluída?
Ant4res 21/03/2013

3
@ Ant4res, desde que você não esteja fazendo referência à instância da tarefa assíncrona, o GC liberará a memória. No entanto, se você tiver uma tarefa em segundo plano em andamento, considere fazê-la em um loop dentro do doInBackground e faça chamadas para publishProgress para atualizar o progresso. Ou, outra abordagem seria colocar sua tarefa em um encadeamento em segundo plano. Muitas abordagens diferentes aqui, mas não podemos recomendar uma sobre a outra sem detalhes.
21813 Steve Prentice

28

As razões para instâncias de disparar e esquecer do ASyncTask são bem detalhadas na resposta de Steve Prentice - No entanto, embora você esteja restrito a quantas vezes você executa um ASyncTask, você é livre para fazer o que quiser enquanto o thread estiver em execução. .

Coloque seu código executável dentro de um loop em doInBackground () e use um bloqueio simultâneo para acionar cada execução. Você pode recuperar os resultados usando publishProgress () / onProgressUpdate () .

Exemplo:

class GetDataFromServerTask extends AsyncTask<Input, Result, Void> {

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition tryAgain = lock.newCondition();
    private volatile boolean finished = false;

    @Override
    protected Void doInBackground(Input... params) {

        lock.lockInterruptibly();

        do { 
            // This is the bulk of our task, request the data, and put in "result"
            Result result = ....

            // Return it to the activity thread using publishProgress()
            publishProgress(result);

            // At the end, we acquire a lock that will delay
            // the next execution until runAgain() is called..
            tryAgain.await();

        } while(!finished);

        lock.unlock();
    }

    @Override
    protected void onProgressUpdate(Result... result) 
    {
        // Treat this like onPostExecute(), do something with result

        // This is an example...
        if (result != whatWeWant && userWantsToTryAgain()) {
            runAgain();
        }
    }

    public void runAgain() {
        // Call this to request data from the server again
        tryAgain.signal();
    }

    public void terminateTask() {
        // The task will only finish when we call this method
        finished = true;
        lock.unlock();
    }

    @Override
    protected void onCancelled() {
        // Make sure we clean up if the task is killed
        terminateTask();
    }
}

Obviamente, isso é um pouco mais complicado do que o uso tradicional do ASyncTask, e você desiste do uso de publishProgress () para relatórios de progresso reais. Mas se a sua preocupação é a memória, essa abordagem garantirá que apenas um ASyncTask permaneça na pilha no tempo de execução.


Mas o ponto é que eu não quero executar novamente o Asyntask enquanto estiver em execução, mas porque este finalizou e não recebeu os dados como deveria, depois chame-o novamente.
21411 Dayerman

Na verdade, você só executa o ASyncTask uma vez dessa maneira e pode verificar se os dados estão corretos dentro do método onPublishProgress (ou delegar a verificação em outro lugar). Eu usei esse padrão para um problema semelhante há algum tempo (muitas tarefas sendo executadas em rápida sucessão, arriscando o tamanho da pilha).
seanhodges

Mas e se nesse momento o servidor não responder e eu quiser tentar novamente 10 segundos depois? ? O AsyncTask já terminou, rigth Então eu preciso chamá-lo novamente
Dayerman

Adicionei um código de exemplo para descrever o que quero dizer. O ASyncTask só será concluído quando você estiver satisfeito com o resultado e chamar "terminateTask ()".
seanhodges

1
Se você ficar IllegalMonitorStateExceptionem runAgain(chamado por onProgressUpdate) ver esta resposta: stackoverflow.com/a/42646476/2711811 . Sugere (e funcionou para mim) que as signal()necessidades precisam ser cercadas por um lock/ unlock. Isso pode ter a ver com o tempo da publishProgresschamada onProgressUpdate.
187 Andy Andy

2

Eu tive o mesmo problema. No meu caso, tenho uma tarefa que quero fazer dentro onCreate()e dentro onResume(). Então, fiz o meu Asynctask estático e obtive a instância dele. Agora ainda temos o mesmo problema.

Então, o que eu fiz no onPostExecute () é o seguinte:

instance = null;

Tendo em mente que eu verifico no método estático getInstance que minha instância não é nula; caso contrário, eu a crio:

if (instance == null){
    instance = new Task();
}
return instance;

O método em postExecute esvaziará a instância e a recriará. Claro que isso pode ser feito fora da sala de aula.


1

Tornei minhas tarefas de rotação estáticas, o que me ajudou a anexar, desanexar e anexá-las novamente aos threads da interface do usuário nas alterações de rotação. Mas, voltando à sua pergunta, o que faço é criar um sinalizador para ver se o encadeamento está em execução. Quando você deseja reiniciar o encadeamento, verifico se a tarefa de rotação está sendo executada, se é para fazer um aviso. Caso contrário, eu o anulo e, em seguida, crio um novo, que contornará o erro que você está vendo. Além disso, após a conclusão bem-sucedida, anulo a tarefa de reconhecimento de rotação concluída, para que ela esteja pronta para continuar novamente.


0

Sim, é verdade, o documento diz que apenas pode ser executado um Asyntask.

Toda vez que você precisar usá-lo, você deve ter uma instância:

// Any time if you need to call her
final FirmwareDownload fDownload = new FirmwareDownload();
fDownload.execute("your parameter");

static class FirmwareDownload extends AsyncTask<String, String, String> {
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.