Modo ideale per annullare un AsyncTask in esecuzione


108

Sto eseguendo operazioni remote di recupero file audio e riproduzione di file audio in un thread in background utilizzando AsyncTask. UNCancellable una barra di avanzamento per il tempo di esecuzione dell'operazione di recupero.

Voglio annullare / interrompere l' AsyncTaskesecuzione quando l'utente annulla (decide contro) l'operazione. Qual è il modo ideale per gestire un caso del genere?

Risposte:


76

Appena scoperto che AlertDialogs's boolean cancel(...);ho usato in tutto il mondo in realtà non fa nulla. Grande.
Così...

public class MyTask extends AsyncTask<Void, Void, Void> {

    private volatile boolean running = true;
    private final ProgressDialog progressDialog;

    public MyTask(Context ctx) {
        progressDialog = gimmeOne(ctx);

        progressDialog.setCancelable(true);
        progressDialog.setOnCancelListener(new OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                // actually could set running = false; right here, but I'll
                // stick to contract.
                cancel(true);
            }
        });

    }

    @Override
    protected void onPreExecute() {
        progressDialog.show();
    }

    @Override
    protected void onCancelled() {
        running = false;
    }

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

        while (running) {
            // does the hard work
        }
        return null;
    }

    // ...

}

55
invece di creare un flag booleano per l'esecuzione, non potresti rimuoverlo e farlo mentre (! isCanceled ()) ???
Confucio

36
Dai documenti su onCancelled (): "Viene eseguito sul thread dell'interfaccia utente dopo che cancel (boolean) è stato richiamato e doInBackground (Object []) è terminato." Questo "dopo" significa che impostare un flag in onCancelled e fare il check-in doInBackground non ha senso.
Lopek

2
@confucius è vero, ma in questo modo il thread in background non viene interrotto, supponiamo che durante il caricamento dell'immagine, il processo di caricamento continui in background e non siamo stati chiamati a onPostExecute.
umesh

1
@DanHulme Credo di aver fatto riferimento allo snippet di codice fornito nella risposta, non al commento di Confucio (che è giusto).
lopek

4
Sì, questa risposta non funziona . In doInBackground, sostituisci while(running)con while(!isCancelled())come altri hanno detto qui nei commenti.
matt5784

76

Se stai facendo calcoli :

  • Devi controllare isCancelled()periodicamente.

Se stai eseguendo una richiesta HTTP :

  • Salva l'istanza del tuo HttpGetoHttpPost da qualche parte (ad es. Un campo pubblico).
  • Dopo aver chiamato cancel, chiama request.abort(). Questo farà sì che IOExceptionvenga gettato dentro il tuo doInBackground.

Nel mio caso, avevo una classe connettore che ho usato in vari AsyncTask. Per mantenerlo semplice, ho aggiunto un nuovo abortAllRequestsmetodo a quella classe e ho chiamato questo metodo direttamente dopo la chiamata cancel.


grazie, funziona, ma come evitare l'eccezione in questo caso?
begiPass

Devi chiamare HttpGet.abort()da un thread in background o otterrai un file android.os.NetworkOnMainThreadException.
Heath Borders

@wrygiel Se stai facendo una richiesta HTTP, non dovresti cancel(true)interrompere la richiesta? Dalla documentazione:If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
Storo

HttpURLConnection.disconnect();
Oded Breiner

Se hai un'operazione che consuma CPU in AsyncTask, devi chiamare cancel(true). L'ho usato e funziona.
SMMousavi

20

Il fatto è che la chiamata AsyncTask.cancel () chiama solo la funzione onCancel nella tua attività. Qui è dove vuoi gestire la richiesta di annullamento.

Ecco una piccola attività che utilizzo per attivare un metodo di aggiornamento

private class UpdateTask extends AsyncTask<Void, Void, Void> {

        private boolean running = true;

        @Override
        protected void onCancelled() {
            running = false;
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
            onUpdate();
        }

        @Override
        protected Void doInBackground(Void... params) {
             while(running) {
                 publishProgress();
             }
             return null;
        }
     }

2
Questo funzionerà, ma logicamente quando aspetti la risposta del server e hai appena eseguito l'operazione db, allora dovrebbe riflettere le modifiche corrette alla tua attività. Ho scritto un blog su questo, guarda la mia risposta.
Vikas

4
Come accennato nei commenti sulla risposta accettata, non è necessario creare il proprio runningflag. AsyncTask ha un flag interno che viene impostato quando l'attività è stata annullata. Sostituisci while (running)con while (!isCancelled()). developer.android.com/reference/android/os/… Quindi, in questo semplice caso, non è necessario onCancelled()eseguire l'override.
ToolmakerSteve

11

Semplice: non utilizzare un file AsyncTask. AsyncTaskè progettato per operazioni brevi che terminano velocemente (decine di secondi) e quindi non necessitano di essere annullate. "Riproduzione di file audio" non si qualifica. Non hai nemmeno bisogno di un thread in background per la normale riproduzione di file audio.


stai suggerendo di utilizzare un normale thread java e di "interrompere" l'esecuzione del thread utilizzando una variabile booleana volatile - il modo convenzionale di Java?
Samuh

34
Senza offesa Mike, ma non è una risposta accettabile. AsyncTask ha un metodo di annullamento e dovrebbe funzionare. Per quanto ne so, non è così, ma anche se lo sto facendo in modo sbagliato, dovrebbe esserci un modo giusto per annullare un'attività. Il metodo non esisterebbe altrimenti. E anche le attività brevi potrebbero dover essere annullate: ho un'attività in cui inizia un AsyncTask immediatamente dopo il caricamento e se l'utente risponde immediatamente dopo aver aperto l'attività, vedrà una chiusura forzata un secondo dopo quando l'attività termina ma nessun contesto esiste per essere utilizzato nel suo onPostExecute.
Eric Mill,

10
@ Klondike: non ho idea di chi sia "Mike". "ma questa non è una risposta accettabile" - sei il benvenuto per la tua opinione. "AsyncTask ha un metodo di annullamento e dovrebbe funzionare." - L'annullamento dei thread in Java è stato un problema per ~ 15 anni. Non ha molto a che fare con Android. Per quanto riguarda il tuo scenario "Force Close", questo può essere risolto da una variabile booleana, che provi onPostExecute()per vedere se dovresti andare avanti con il lavoro.
CommonsWare

1
@Tejaswi Yerukalapudi: È più che non farà nulla automaticamente. Vedi la risposta accettata a questa domanda.
CommonsWare

10
Dovresti controllare periodicamente il metodo isCancelled nel tuo doInBackground su AsyncTask. È proprio lì nella documentazione: developer.android.com/reference/android/os/…
Christopher Perry,

4

L'unico modo per farlo è controllare il valore del metodo isCancelled () e interrompere la riproduzione quando restituisce true.


4

È così che scrivo il mio AsyncTask
il punto chiave è aggiungere Thread.sleep (1);

@Override   protected Integer doInBackground(String... params) {

        Log.d(TAG, PRE + "url:" + params[0]);
        Log.d(TAG, PRE + "file name:" + params[1]);
        downloadPath = params[1];

        int returnCode = SUCCESS;
        FileOutputStream fos = null;
        try {
            URL url = new URL(params[0]);
            File file = new File(params[1]);
            fos = new FileOutputStream(file);

            long startTime = System.currentTimeMillis();
            URLConnection ucon = url.openConnection();
            InputStream is = ucon.getInputStream();
            BufferedInputStream bis = new BufferedInputStream(is);

            byte[] data = new byte[10240]; 
            int nFinishSize = 0;
            while( bis.read(data, 0, 10240) != -1){
                fos.write(data, 0, 10240);
                nFinishSize += 10240;
                **Thread.sleep( 1 ); // this make cancel method work**
                this.publishProgress(nFinishSize);
            }              
            data = null;    
            Log.d(TAG, "download ready in"
                  + ((System.currentTimeMillis() - startTime) / 1000)
                  + " sec");

        } catch (IOException e) {
                Log.d(TAG, PRE + "Error: " + e);
                returnCode = FAIL;
        } catch (Exception e){
                 e.printStackTrace();           
        } finally{
            try {
                if(fos != null)
                    fos.close();
            } catch (IOException e) {
                Log.d(TAG, PRE + "Error: " + e);
                e.printStackTrace();
            }
        }

        return returnCode;
    }

1
Ho scoperto che la semplice chiamata a cancel (true) sull'attività asincrona e il controllo periodico di isCancelled () funziona, ma a seconda di ciò che sta facendo l'attività, possono essere necessari fino a 60 secondi prima che venga interrotta. L'aggiunta di Thread.sleep (1) consente di interromperlo immediatamente. (L'attività asincrona entra piuttosto in uno stato di attesa e non viene eliminata immediatamente). Grazie per questo.
John J Smith

0

La nostra variabile di classe AsyncTask globale

LongOperation LongOperationOdeme = new LongOperation();

E l'azione KEYCODE_BACK che interrompe AsyncTask

   @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            LongOperationOdeme.cancel(true);
        }
        return super.onKeyDown(keyCode, event);
    }

Per me funziona.


0

Non mi piace forzare l'interruzione delle mie attività asincrone cancel(true)inutilmente perché potrebbero avere risorse da liberare, come la chiusura di socket o flussi di file, la scrittura di dati nel database locale ecc. D'altra parte, ho dovuto affrontare situazioni in cui il l'attività asincrona si rifiuta di terminare se stessa una parte del tempo, ad esempio a volte quando l'attività principale viene chiusa e chiedo che l'attività asincrona finisca dall'interno del onPause()metodo dell'attività . Quindi non si tratta semplicemente di chiamare running = false. Devo optare per una soluzione mista: entrambi chiamano running = false, quindi assegnano all'attività asincrona alcuni millisecondi per terminare, quindi chiamano o cancel(false)o cancel(true).

if (backgroundTask != null) {
    backgroundTask.requestTermination();
    try {
        Thread.sleep((int)(0.5 * 1000));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    if (backgroundTask.getStatus() != AsyncTask.Status.FINISHED) {
        backgroundTask.cancel(false);
    }
    backgroundTask = null;
}

Di conseguenza, dopo doInBackground() termine, a volte onCancelled()viene chiamato il metodo e talvolta onPostExecute(). Ma almeno la terminazione asincrona dell'attività è garantita.


Sembra una condizione di gara.
msangel

0

Con riferimento alla risposta di Yanchenko del 29 aprile '10: l'utilizzo di un approccio 'while (running)' è accurato quando il tuo codice sotto 'doInBackground' deve essere eseguito più volte durante ogni esecuzione dell'AsyncTask. Se il codice in 'doInBackground' deve essere eseguito solo una volta per esecuzione di AsyncTask, avvolgere tutto il codice in 'doInBackground' in un ciclo 'while (running)' non interromperà l'esecuzione del codice in background (thread in background) quando il AsyncTask stesso viene annullato, perché la condizione 'while (running)' verrà valutata solo una volta che tutto il codice all'interno del ciclo while è stato eseguito almeno una volta. Dovresti quindi (a.) Suddividere il codice in "doInBackground" in più blocchi "while (running)" o (b.) Eseguire numerosi "isCancelled"https://developer.android.com/reference/android/os/AsyncTask.html .

Per l'opzione (a.) Si può quindi modificare la risposta di Yanchenko come segue:

public class MyTask extends AsyncTask<Void, Void, Void> {

private volatile boolean running = true;

//...

@Override
protected void onCancelled() {
    running = false;
}

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

    // does the hard work

    while (running) {
        // part 1 of the hard work
    }

    while (running) {
        // part 2 of the hard work
    }

    // ...

    while (running) {
        // part x of the hard work
    }
    return null;
}

// ...

Per l'opzione (b.) Il tuo codice in 'doInBackground' sarà simile a questo:

public class MyTask extends AsyncTask<Void, Void, Void> {

//...

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

    // part 1 of the hard work
    // ...
    if (isCancelled()) {return null;}

    // part 2 of the hard work
    // ...
    if (isCancelled()) {return null;}

    // ...

    // part x of the hard work
    // ...
    if (isCancelled()) {return null;}
}

// ...
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.