Führen Sie AsyncTask mehrmals aus

127

In meiner Aktivität verwende ich eine Klasse, die sich von AsyncTask erstreckt, und einen Parameter, der eine Instanz dieser AsyncTask ist. Wenn ich anrufe, ist mInstanceOfAT.execute("")alles in Ordnung. Die App stürzt jedoch ab, wenn ich eine Aktualisierungstaste drücke, die die AsyncTask erneut aufruft (falls der Netzwerkjob nicht funktioniert hat). Ursache erscheint dann eine Ausnahme, die besagt

Aufgabe kann nicht ausgeführt werden: Die Aufgabe wurde bereits ausgeführt (eine Aufgabe kann nur einmal ausgeführt werden).

Ich habe versucht, cancel (true) für die Instanz der Asyctask aufzurufen, aber es funktioniert auch nicht. Die einzige Lösung besteht bisher darin, neue Instanzen der Asyntask zu erstellen. Ist das der richtige Weg?

Vielen Dank.

Dayerman
quelle

Antworten:

217

AsyncTask Instanzen können nur einmal verwendet werden.

Nennen Sie stattdessen einfach Ihre Aufgabe wie new MyAsyncTask().execute("");

Aus den AsyncTask-API-Dokumenten:

Threading-Regeln

Es gibt einige Threading-Regeln, die befolgt werden müssen, damit diese Klasse ordnungsgemäß funktioniert:

  • Die Aufgabeninstanz muss im UI-Thread erstellt werden.
  • execute (Params ...) muss im UI-Thread aufgerufen werden.
  • Rufen Sie onPreExecute (), onPostExecute (Ergebnis), doInBackground (Params ...) und onProgressUpdate (Progress ...) nicht manuell auf.
  • Die Aufgabe kann nur einmal ausgeführt werden (eine Ausnahme wird ausgelöst, wenn eine zweite Ausführung versucht wird.)
Steve Prentice
quelle
2
Das, was ich gesagt habe, ist das die einzige Möglichkeit? Ursache Ich möchte Speicher speichern, anstatt ein neues Objekt zu erstellen.
Dayerman
1
Siehe auch stackoverflow.com/questions/2711183/…
Steve Prentice
@StevePrentice: Wenn ich alle x Sekunden eine Instanz der Aufgabe mit new task (). Execute (param) erstelle, um Daten an einen Server zu senden, wie kann der Garbage Collector nach Abschluss der Ausführung Speicher freigeben?
Ant4res
3
@ Ant4res, Solange Sie nicht auf die asynchrone Taskinstanz verweisen, gibt der GC den Speicher frei. Wenn Sie jedoch eine laufende Hintergrundaufgabe haben, können Sie diese in einer Schleife innerhalb von doInBackground ausführen und PublishProgress aufrufen, um den Fortschritt zu aktualisieren. Oder ein anderer Ansatz wäre, Ihre Aufgabe in einen Hintergrund-Thread zu stellen. Viele verschiedene Ansätze hier, aber ohne Details kann man sich nicht gegenseitig empfehlen.
Steve Prentice
28

Die Gründe für Fire-and-Forget-Instanzen von ASyncTask sind in Steve Prentices Antwort ziemlich detailliert beschrieben. Obwohl Sie sich darauf beschränken, wie oft Sie eine ASyncTask ausführen, können Sie tun, was Sie möchten, während der Thread ausgeführt wird. .

Fügen Sie Ihren ausführbaren Code in eine Schleife in doInBackground () ein und verwenden Sie eine gleichzeitige Sperre, um jede Ausführung auszulösen. Sie können die Ergebnisse mit PublishProgress () / onProgressUpdate () abrufen .

Beispiel:

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

Dies ist natürlich etwas komplizierter als die herkömmliche Verwendung von ASyncTask, und Sie geben die Verwendung von PublishProgress () für die tatsächliche Fortschrittsberichterstattung auf. Wenn der Speicher jedoch Ihr Anliegen ist, stellt dieser Ansatz sicher, dass zur Laufzeit nur eine ASyncTask im Heap verbleibt.

Seanhodges
quelle
Aber der Punkt ist, dass ich die Asyntask nicht erneut ausführen möchte, während sie ausgeführt wird, sondern weil diese beendet ist und die Daten nicht wie gewünscht empfangen hat, und sie dann erneut aufrufe.
Dayerman
Tatsächlich führen Sie die ASyncTask nur einmal auf diese Weise aus und können überprüfen, ob die Daten innerhalb der onPublishProgress-Methode korrekt sind (oder die Prüfung an eine andere Stelle delegieren). Ich habe dieses Muster vor einiger Zeit für ein ähnliches Problem verwendet (viele Aufgaben werden schnell hintereinander ausgeführt, wodurch die Größe des Heapspeichers gefährdet wird).
Seanhodges
Aber was ist, wenn der Server in diesem Moment nicht antwortet und ich es 10 Sekunden später erneut versuchen möchte? Die AsyncTask ist bereits fertig, oder? Dann muss ich sie noch einmal
anrufen
Ich habe einen Beispielcode hinzugefügt, um zu beschreiben, was ich meine. ASyncTask wird erst beendet, wenn Sie mit dem Ergebnis zufrieden sind und "terminateTask ()" aufrufen.
Seanhodges
1
Wenn Sie erhalten IllegalMonitorStateExceptionin runAgain(genannt von onProgressUpdate) sehen diese Antwort: stackoverflow.com/a/42646476/2711811 . Es legt nahe (und hat für mich funktioniert), dass die signal()Bedürfnisse von einem lock/ umgeben sein müssen unlock. Dies hängt möglicherweise mit dem Zeitpunkt des publishProgressAnrufs zusammen onProgressUpdate.
Andy
2

Ich hatte das gleiche Problem. In meinem Fall habe ich eine Aufgabe, die ich in onCreate()und in erledigen möchte onResume(. Also habe ich meine Asynctask statisch gemacht und die Instanz daraus abgerufen. Jetzt haben wir immer noch das gleiche Problem.

Was ich in onPostExecute () getan habe, ist Folgendes:

instance = null;

Denken Sie daran, dass ich in der statischen Methode getInstance einchecke, dass meine Instanz nicht null ist, sonst erstelle ich sie:

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

Die Methode in postExecute leert die Instanz und erstellt sie neu. Dies kann natürlich auch außerhalb der Klasse erfolgen.

SamuelD
quelle
1

Ich habe meine Rotationsaufgaben statisch gemacht, wodurch ich sie bei Rotationsänderungen an UI-Threads anhängen, trennen und wieder anhängen konnte. Um jedoch auf Ihre Frage zurückzukommen, erstelle ich ein Flag, um festzustellen, ob der Thread ausgeführt wird. Wenn Sie den Thread neu starten möchten, überprüfe ich, ob die Rotationsaufgabe ausgeführt wird, wenn ich auf eine Warnung stoße. Wenn dies nicht der Fall ist, mache ich es null und erstelle dann ein neues, das den angezeigten Fehler umgeht. Außerdem habe ich nach erfolgreichem Abschluss die abgeschlossene rotationsbewusste Aufgabe auf Null gesetzt, damit sie wieder einsatzbereit ist.

Arnab C.
quelle
0

Ja, es ist wahr, der Doc sagt, dass nur eine Asyntask ausgeführt werden kann.

Jedes Mal, wenn Sie es verwenden müssen, müssen Sie Folgendes tun:

// 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> {
}
Victor Ruiz.
quelle