Ideale Möglichkeit, eine ausgeführte AsyncTask abzubrechen

108

Ich führe Remote-Operationen zum Abrufen von Audiodateien und zur Wiedergabe von Audiodateien in einem Hintergrundthread mit aus AsyncTask. CancellableFür die Zeit, in der der Abrufvorgang ausgeführt wird, wird ein Fortschrittsbalken angezeigt.

Ich möchte den AsyncTaskLauf abbrechen / abbrechen , wenn der Benutzer den Vorgang abbricht (sich dagegen entscheidet). Was ist der ideale Weg, um einen solchen Fall zu behandeln?

Samuh
quelle

Antworten:

76

Ich habe gerade festgestellt, dass das AlertDialogs, was boolean cancel(...);ich überall benutzt habe, eigentlich nichts bewirkt. Toll.
So...

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

    // ...

}
yanchenko
quelle
55
Könnten Sie nicht ein boolesches Flag zum Ausführen erstellen, sondern es entfernen und dies während (! isCanceled ()) machen?
Konfuzius
36
Aus Dokumenten zu onCancelled (): "Wird auf dem UI-Thread ausgeführt, nachdem cancel (boolean) aufgerufen und doInBackground (Object []) beendet wurde." Dieses 'Nachher' bedeutet, dass das Setzen eines Flags in onCancelled und das Einchecken in doInBackground keinen Sinn macht.
Lopek
2
@confucius das ist richtig, aber auf diese Weise wird der Hintergrund-Thread nicht unterbrochen. Nehmen wir an, der Fall beim Hochladen des Bildes wird im Hintergrund fortgesetzt und onPostExecute wurde nicht aufgerufen.
Umesh
1
@DanHulme Ich glaube, ich habe mich auf das in der Antwort angegebene Code-Snippet bezogen, nicht auf den Kommentar des Konfuzius (der richtig ist).
Lopek
4
Ja, diese Antwort funktioniert nicht . Im doInBackground, ersetzt while(running)mit while(!isCancelled())wie andere gesagt haben hier in den Kommentaren.
Matt5784
76

Wenn Sie Berechnungen durchführen :

  • Sie müssen isCancelled()regelmäßig überprüfen .

Wenn Sie eine HTTP-Anfrage stellen :

  • Speichern Sie die Instanz Ihres HttpGetoder HttpPostirgendwo (z. B. ein öffentliches Feld).
  • Rufen Sie nach dem cancelAnruf an request.abort(). Dies wird dazu führen, IOExceptiondass Sie in Ihre geworfen werden doInBackground.

In meinem Fall hatte ich eine Connector-Klasse, die ich in verschiedenen AsyncTasks verwendet habe. Um es einfach zu halten, habe ich abortAllRequestsdieser Klasse eine neue Methode hinzugefügt und diese Methode direkt nach dem Aufruf aufgerufen cancel.

Wrygiel
quelle
Danke, es funktioniert, aber wie vermeide ich die Ausnahme in diesem Fall?
BegiPass
Sie müssen HttpGet.abort()von einem Hintergrund-Thread anrufen , sonst erhalten Sie eine android.os.NetworkOnMainThreadException.
Heath Borders
@wrygiel Wenn Sie eine HTTP-Anfrage stellen, sollten Sie cancel(true)die Anfrage nicht unterbrechen? Aus der Dokumentation: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
Wenn Sie in AsyncTask einen CPU-verbrauchenden Vorgang haben, müssen Sie diesen aufrufen cancel(true). Ich habe es benutzt und es funktioniert.
SMMousavi
20

Die Sache ist, dass der Aufruf von AsyncTask.cancel () nur die Funktion onCancel in Ihrer Aufgabe aufruft. Hier möchten Sie die Abbruchanforderung bearbeiten.

Hier ist eine kleine Aufgabe, mit der ich eine Aktualisierungsmethode auslöse

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;
        }
     }
DonCroco
quelle
2
Dies funktioniert, aber wenn Sie logischerweise auf eine Serverantwort warten und gerade eine Datenbankoperation ausgeführt haben, sollte dies die korrekten Änderungen an Ihrer Aktivität widerspiegeln. Ich habe einen Blog darüber geschrieben, siehe meine Antwort.
Vikas
4
Wie in den Kommentaren zur akzeptierten Antwort erwähnt, muss keine eigene runningFlagge erstellt werden. AsyncTask verfügt über ein internes Flag, das gesetzt wird, wenn die Aufgabe abgebrochen wurde. Ersetzen while (running)durch while (!isCancelled()). developer.android.com/reference/android/os/… In diesem einfachen Fall müssen Sie also nicht onCancelled()überschreiben.
ToolmakerSteve
11

Einfach: Verwenden Sie keine AsyncTask. AsyncTaskist für kurze Vorgänge ausgelegt, die schnell enden (mehrere zehn Sekunden) und daher nicht abgebrochen werden müssen. "Wiedergabe von Audiodateien" ist nicht qualifiziert. Sie benötigen nicht einmal einen Hintergrund-Thread für die normale Wiedergabe von Audiodateien.

CommonsWare
quelle
Schlagen Sie vor, dass wir einen regulären Java-Thread verwenden und den Thread-Lauf mit einer flüchtigen booleschen Variablen "abbrechen" - auf herkömmliche Java-Weise?
Samuh
34
Nichts für ungut, Mike, aber das ist keine akzeptable Antwort. AsyncTask verfügt über eine Abbruchmethode und sollte funktionieren. Soweit ich das beurteilen kann, ist dies nicht der Fall - aber selbst wenn ich es falsch mache, sollte es einen richtigen Weg geben, eine Aufgabe abzubrechen. Die Methode würde sonst nicht existieren. Und selbst kurze Aufgaben müssen möglicherweise abgebrochen werden. Ich habe eine Aktivität, bei der eine AsyncTask sofort nach dem Laden gestartet wird. Wenn der Benutzer unmittelbar nach dem Öffnen der Aufgabe zurückschlägt, wird eine Sekunde später ein Force Close angezeigt, wenn die Aufgabe beendet ist, jedoch kein Kontext existiert, damit es in seinem onPostExecute verwendet werden kann.
Eric Mill
10
@Klondike: Ich habe keine Ahnung wer "Mike" ist. "aber das ist keine akzeptable Antwort" - Sie sind zu Ihrer Meinung willkommen. "AsyncTask hat eine Abbruchmethode und sollte funktionieren." - Das Abbrechen von Threads in Java ist seit ca. 15 Jahren ein Problem. Es hat nicht viel mit Android zu tun. In Bezug auf Ihr "Force Close" -Szenario kann dies durch eine boolesche Variable gelöst werden, in der Sie testen onPostExecute(), ob Sie mit der Arbeit fortfahren sollten.
CommonsWare
1
@ Tejaswi Yerukalapudi: Es ist mehr, dass es nichts automatisch macht. Siehe die akzeptierte Antwort auf diese Frage.
CommonsWare
10
Sie sollten die isCancelled-Methode regelmäßig in Ihrem doInBackground auf AsyncTask überprüfen. Es ist genau dort in den Dokumenten: developer.android.com/reference/android/os/…
Christopher Perry
4

Die einzige Möglichkeit, dies zu tun, besteht darin, den Wert der Methode isCancelled () zu überprüfen und die Wiedergabe zu stoppen, wenn true zurückgegeben wird.

dbyrne
quelle
4

So schreibe ich meine AsyncTask.
Der entscheidende Punkt ist das Hinzufügen von 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;
    }
Andrew Chen
quelle
1
Ich habe festgestellt, dass das einfache Aufrufen von cancel (true) für die asynchrone Aufgabe und das regelmäßige Überprüfen von isCancelled () funktioniert. Je nachdem, was Ihre Aufgabe tut, kann es jedoch bis zu 60 Sekunden dauern, bis sie unterbrochen wird. Durch Hinzufügen von Thread.sleep (1) kann es sofort unterbrochen werden. (Async Task wechselt eher in den Wartezustand und wird nicht sofort verworfen.) Danke dafür.
John J Smith
0

Unsere globale AsyncTask-Klassenvariable

LongOperation LongOperationOdeme = new LongOperation();

Und KEYCODE_BACK-Aktion, die AsyncTask unterbricht

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

Für mich geht das.

Göksel Güren
quelle
0

Ich möchte meine asynchronen Aufgaben nicht cancel(true)unnötig unterbrechen, da sie möglicherweise Ressourcen freigeben müssen, z. B. das Schließen von Sockets oder Dateistreams, das Schreiben von Daten in die lokale Datenbank usw. Andererseits habe ich Situationen erlebt, in denen die Die asynchrone Aufgabe weigert sich, sich zeitweise selbst zu beenden, zum Beispiel manchmal, wenn die Hauptaktivität geschlossen wird und ich die asynchrone Aufgabe auffordere, sie innerhalb der onPause()Methode der Aktivität zu beenden . Es geht also nicht nur darum anzurufen running = false. Ich muss mich für eine gemischte Lösung entscheiden: Beide rufen auf running = false, geben der asynchronen Aufgabe einige Millisekunden Zeit und beenden dann entweder cancel(false)oder 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;
}

Als Nebenergebnis wird nach doInBackground()Abschluss manchmal die onCancelled()Methode aufgerufen, und manchmal onPostExecute(). Zumindest die Beendigung der asynchronen Aufgabe ist jedoch garantiert.

Piovezan
quelle
Sieh aus wie eine Rennbedingung.
Msangel
0

In Bezug auf Yanchenkos Antwort vom 29. April '10: Die Verwendung eines "while (running)" - Ansatzes ist ordentlich, wenn Ihr Code unter "doInBackground" bei jeder Ausführung der AsyncTask mehrmals ausgeführt werden muss. Wenn Ihr Code unter 'doInBackground' nur einmal pro Ausführung der AsyncTask ausgeführt werden muss, wird durch das Umschließen Ihres gesamten Codes unter 'doInBackground' in eine 'while (running)' - Schleife nicht verhindert, dass der Hintergrundcode (Hintergrundthread) ausgeführt wird, wenn der AsyncTask selbst wird abgebrochen, da die Bedingung 'while (running)' erst ausgewertet wird, wenn der gesamte Code in der while-Schleife mindestens einmal ausgeführt wurde. Sie sollten daher entweder (a.) Ihren Code unter 'doInBackground' in mehrere 'während (laufende)' Blöcke aufteilen oder (b.) Zahlreiche 'isCancelled' ausführen.https://developer.android.com/reference/android/os/AsyncTask.html .

Für Option (a.) Kann man daher Yanchenkos Antwort wie folgt ändern:

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

// ...

Für Option (b.) Sieht Ihr Code in 'doInBackground' ungefähr so ​​aus:

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

// ...
Alex Ivan Howard
quelle