Esegui AsyncTask più volte


127

Nella mia attività uso una classe che si estende da AsyncTask e un parametro che è un'istanza di tale AsyncTask. Quando chiamo mInstanceOfAT.execute("")tutto bene. Ma l'app si arresta in modo anomalo quando premo un pulsante di aggiornamento che richiama nuovamente l'AsyncTask (nel caso in cui il lavoro di rete non funzionasse). La causa quindi appare un'eccezione che dice

Impossibile eseguire l'attività: l'attività è già stata eseguita (un'attività può essere eseguita una sola volta)

Ho provato a chiamare cancel (true) per l'istanza di Asyctask, ma non funziona neanche. L'unica soluzione finora è creare nuove istanze dell'Asyntask. È quello il modo corretto?

Grazie.

Risposte:


217

AsyncTask le istanze possono essere utilizzate solo una volta.

Invece, basta chiamare il tuo compito come new MyAsyncTask().execute("");

Dai documenti dell'API AsyncTask:

Regole di threading

Esistono alcune regole di threading che devono essere seguite affinché questa classe funzioni correttamente:

  • L'istanza dell'attività deve essere creata sul thread dell'interfaccia utente.
  • execute (Params ...) deve essere richiamato sul thread dell'interfaccia utente.
  • Non chiamare onPreExecute (), onPostExecute (Risultato), doInBackground (Params ...), onProgressUpdate (Progress ...) manualmente.
  • L'attività può essere eseguita una sola volta (verrà generata un'eccezione se si tenta una seconda esecuzione).

2
Che quello che ho detto di aver fatto, è l'unica possibilità? Causa Voglio salvare la memoria, invece di creare un nuovo oggetto.
Dayerman,


@StevePrentice: se creo un'istanza dell'attività ogni x secondi con la nuova attività (). Execute (param), per inviare dati a un server, come garbage collector può liberare memoria quando l'esecuzione è completata?
Ant4res,

3
@ Ant4res, fintanto che non si fa riferimento all'istanza dell'attività asincrona, il GC rilascerà la memoria. Tuttavia, se hai un'attività in background in corso, potresti considerare di farlo in un ciclo all'interno di doInBackground ed effettuare chiamate a publishingProgress per aggiornare l'avanzamento. Oppure, un altro approccio sarebbe quello di mettere la tua attività in un thread in background. Molti approcci diversi qui, ma non posso raccomandare l'uno sull'altro senza dettagli.
Steve Prentice,

28

Le ragioni delle istanze di attivazione e disattivazione di ASyncTask sono descritte abbastanza bene nella risposta di Steve Prentice - Tuttavia, mentre sei limitato su quante volte esegui un ASyncTask, sei libero di fare quello che ti piace mentre il thread è in esecuzione. .

Inserisci il tuo codice eseguibile all'interno di un ciclo all'interno di doInBackground () e usa un blocco simultaneo per attivare ogni esecuzione. È possibile recuperare i risultati utilizzando publishingProgress () / onProgressUpdate () .

Esempio:

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();
    }
}

Ovviamente, questo è leggermente più complicato rispetto all'utilizzo tradizionale di ASyncTask e si rinuncia all'utilizzo di publishingProgress () per i rapporti sui progressi effettivi. Ma se la memoria è la tua preoccupazione, questo approccio assicurerà che solo un ASyncTask rimanga nell'heap in fase di esecuzione.


Ma il punto è che non voglio rieseguire l'Asyntask mentre è in esecuzione, ma poiché questo ha terminato e non ha ricevuto i dati come dovrebbe, quindi chiamarlo di nuovo.
Dayerman,

In realtà, esegui ASyncTask solo una volta in questo modo e puoi verificare se i dati sono corretti all'interno del metodo onPublishProgress (o delegare il controllo da qualche altra parte). Ho usato questo modello per un problema simile qualche tempo fa (molte attività venivano lanciate in rapida successione, rischiando le dimensioni dell'heap).
seanhodges,

Ma cosa succede se in quel momento il server non risponde, e voglio riprovare 10 secondi dopo? L'AsyncTask è già finito, giusto? Quindi devo chiamarlo di nuovo
Dayerman,

Ho aggiunto un codice di esempio per descrivere cosa intendo. ASyncTask termina solo quando sei soddisfatto del risultato e chiama "terminateTask ()".
seanhodges,

1
Se si ottiene IllegalMonitorStateExceptiona runAgain(chiamato da onProgressUpdate) vedi questa risposta: stackoverflow.com/a/42646476/2711811 . Suggerisce (e ha funzionato per me) che il signal()bisogno di essere circondato da un lock/ unlock. Questo potrebbe avere a che fare con i tempi di publishProgresschiamata onProgressUpdate.
Andy,

2

Ho avuto lo stesso problema. Nel mio caso ho un compito che voglio svolgere dentro onCreate()e dentro onResume(). Quindi ho reso statico il mio Asynctask e ne ho ricavato l'istanza. Ora abbiamo ancora lo stesso problema.

Quindi quello che ho fatto in onPostExecute () è questo:

instance = null;

Tenendo presente che controllo nel metodo getInstance statico che la mia istanza non è nulla, altrimenti la creo:

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

Il metodo in postExecute svuota l'istanza e la ricrea. Naturalmente questo può essere fatto al di fuori della classe.


1

Ho reso statiche le mie attività di rotazione, che mi hanno aiutato a collegarle, staccarle e ricollegarle ai thread dell'interfaccia utente durante le modifiche di rotazione. Ma per tornare alla tua domanda, quello che faccio è creare un flag per vedere se il thread è in esecuzione. Quando vuoi riavviare il thread, controllo se l'attività di rotazione è in esecuzione se è un toast di avviso. In caso contrario, lo annullo e quindi ne creo uno nuovo, che aggirerà l'errore che stai vedendo. Inoltre, al completamento con esito positivo, annullo l'attività di rilevamento rotazione completata in modo che sia pronto per essere ripreso.


0

Sì, è vero, il documento dice che può essere eseguito solo un Asyntask.

Ogni volta che devi usarlo devi istanza:

// 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> {
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.