Exécutez AsyncTask plusieurs fois

127

Dans mon activité, j'utilise une classe qui s'étend d'AsyncTask et un paramètre qui est une instance de cette AsyncTask. Quand j'appelle, mInstanceOfAT.execute("")tout va bien. Mais l'application plante lorsque j'appuie sur un bouton de mise à jour qui appelle à nouveau AsyncTask (au cas où le travail réseau ne fonctionnerait pas). Cause apparaît alors une exception qui dit

Impossible d'exécuter la tâche: la tâche a déjà été exécutée (une tâche ne peut être exécutée qu'une seule fois)

J'ai essayé d'appeler cancel (true) pour l'instance de l'Asyctask, mais cela ne fonctionne pas non plus. La seule solution à ce jour est de créer de nouvelles instances de l'Asyntask. Est-ce la bonne manière?

Merci.

Dayerman
la source

Réponses:

217

AsyncTask les instances ne peuvent être utilisées qu'une seule fois.

Au lieu de cela, appelez simplement votre tâche comme new MyAsyncTask().execute("");

À partir de la documentation de l'API AsyncTask:

Règles de filetage

Il y a quelques règles de thread qui doivent être suivies pour que cette classe fonctionne correctement:

  • L'instance de tâche doit être créée sur le thread d'interface utilisateur.
  • execute (Params ...) doit être appelé sur le thread d'interface utilisateur.
  • N'appelez pas onPreExecute (), onPostExecute (Result), doInBackground (Params ...), onProgressUpdate (Progress ...) manuellement.
  • La tâche ne peut être exécutée qu'une seule fois (une exception sera levée si une seconde exécution est tentée.)
Steve Prentice
la source
2
C'est ce que j'ai dit avoir fait, est-ce la seule possibilité? Parce que je veux enregistrer la mémoire au lieu de créer un nouvel objet.
Dayerman
1
Regarde aussi stackoverflow.com/questions/2711183/…
Steve Prentice
@StevePrentice: si je crée une instance de la tâche toutes les x secondes avec new task (). Execute (param), pour envoyer des données à un serveur, comment garbage collector peut libérer de la mémoire lorsque l'exécution est terminée?
Ant4res
3
@ Ant4res, Tant que vous ne référencez pas l'instance de tâche asynchrone, le GC libère la mémoire. Cependant, si vous avez une tâche d'arrière-plan en cours, vous pouvez envisager de le faire dans une boucle à l'intérieur de doInBackground et faire des appels à publishProgress pour mettre à jour la progression. Ou, une autre approche consisterait à placer votre tâche dans un fil d'arrière-plan. Beaucoup d'approches différentes ici, mais je ne peux pas en recommander une sans détails.
Steve Prentice
28

Les raisons pour lesquelles les instances d'ASyncTask fire-and-forget sont assez bien détaillées dans la réponse de Steve Prentice - Cependant, même si vous êtes limité sur le nombre de fois que vous exécutez une ASyncTask, vous êtes libre de faire ce que vous voulez pendant que le thread est en cours d'exécution. .

Placez votre code exécutable dans une boucle dans doInBackground () et utilisez un verrou simultané pour déclencher chaque exécution. Vous pouvez récupérer les résultats à l'aide de publishProgress () / onProgressUpdate () .

Exemple:

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

Bien sûr, cela est légèrement plus compliqué que l'utilisation traditionnelle d'ASyncTask, et vous abandonnez l'utilisation de publishProgress () pour les rapports d'avancement réels. Mais si la mémoire est votre préoccupation, cette approche garantira qu'une seule ASyncTask reste dans le tas au moment de l'exécution.

seanhodges
la source
Mais le fait est que je ne veux pas réexécuter la tâche Asyntask pendant qu'elle est en cours d'exécution, mais parce que celle-ci a terminé et n'a pas reçu les données comme il se doit, appelez-la à nouveau.
Dayerman
En fait, vous n'exécutez l'ASyncTask qu'une seule fois de cette manière et vous pouvez vérifier si les données sont correctes dans la méthode onPublishProgress (ou déléguer la vérification ailleurs). J'ai utilisé ce modèle pour un problème similaire il y a quelque temps (beaucoup de tâches étant déclenchées en succession rapide, risquant la taille du tas).
seanhodges le
Mais qu'en est-il si à ce moment-là le serveur ne répond pas et que je veux réessayer 10 secondes plus tard? L'AsyncTask est déjà terminée, oui? Alors je dois l'appeler à nouveau
Dayerman
J'ai ajouté un exemple de code pour décrire ce que je veux dire. ASyncTask ne se terminera qu'une fois que vous serez satisfait du résultat et que vous appellerez "terminateTask ()".
seanhodges le
1
Si vous obtenez IllegalMonitorStateExceptionen runAgain(appelé par onProgressUpdate) voir cette réponse: stackoverflow.com/a/42646476/2711811 . Cela suggère (et a fonctionné pour moi) que les signal()besoins d'être entourés d'un lock/ unlock. Cela peut avoir à voir avec le moment de l' publishProgressappel onProgressUpdate.
Andy
2

J'ai eu le même problème. Dans mon cas, j'ai une tâche que je veux faire dans onCreate()et dans onResume(). J'ai donc rendu mon Asynctask statique et j'en ai récupéré l'instance. Maintenant, nous avons toujours le même problème.

Donc, ce que j'ai fait dans onPostExecute () est le suivant:

instance = null;

En gardant à l'esprit que je vérifie dans la méthode statique getInstance que mon instance n'est pas nulle, sinon je la crée:

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

La méthode dans postExecute videra l'instance et la recréera. Bien sûr, cela peut être fait en dehors de la classe.

SamuelD
la source
1

J'ai rendu mes tâches de rotation statiques, ce qui m'a ensuite aidé à les attacher, les détacher et les rattacher aux threads de l'interface utilisateur lors des changements de rotation. Mais pour revenir à votre question, je crée un indicateur pour voir si le fil est en cours d'exécution. Lorsque vous souhaitez redémarrer le thread, je vérifie si la tâche de rotation est en cours d'exécution si c'est le cas, je porte un avertissement. Si ce n'est pas le cas, je le rends nul, puis j'en crée un nouveau, qui contournera l'erreur que vous voyez. De plus, une fois terminé, j'annule la tâche de rotation terminée afin qu'elle soit prête à recommencer.

Arnab C.
la source
0

Oui c'est vrai, le doc dit qu'on ne peut exécuter qu'une seule Asyntask.

Chaque fois que vous avez besoin de l'utiliser, vous devez créer une instance:

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