Ejecute AsyncTask varias veces

127

En mi Actividad, uso una clase que se extiende desde AsyncTask y un parámetro que es una instancia de ese AsyncTask. Cuando llamo mInstanceOfAT.execute("")todo está bien. Pero la aplicación se bloquea cuando presiono un botón de actualización que vuelve a llamar a AsyncTask (en caso de que el trabajo de red no funcione). Porque luego aparece una excepción que dice

No se puede ejecutar la tarea: la tarea ya se ha ejecutado (una tarea se puede ejecutar solo una vez)

He intentado llamar a cancel (true) para la instancia de Asyctask, pero tampoco funciona. La única solución hasta ahora es crear nuevas instancias de Asyntask. ¿Es esa la forma correcta?

Gracias.

Dayerman
fuente

Respuestas:

217

AsyncTask las instancias solo se pueden usar una vez.

En cambio, simplemente llame a su tarea como new MyAsyncTask().execute("");

De los documentos de la API AsyncTask:

Reglas de enhebrado

Hay algunas reglas de subprocesos que se deben seguir para que esta clase funcione correctamente:

  • La instancia de la tarea debe crearse en el subproceso de la interfaz de usuario.
  • execute (Params ...) debe invocarse en el hilo de la interfaz de usuario.
  • No llame a onPreExecute (), onPostExecute (Result), doInBackground (Params ...), onProgressUpdate (Progress ...) manualmente.
  • La tarea se puede ejecutar solo una vez (se lanzará una excepción si se intenta una segunda ejecución).
Steve Prentice
fuente
2
Que lo que dije que hice, ¿es esa la única posibilidad? Porque quiero guardar la memoria, en lugar de crear un nuevo objeto.
Dayerman
1
Ver también stackoverflow.com/questions/2711183/…
Steve Prentice
@StevePrentice: si creo una instancia de la tarea cada x segundos con una nueva tarea (). Execute (param), para enviar datos a un servidor, ¿cómo el recolector de basura puede liberar memoria cuando se completa la ejecución?
Ant4res
3
@ Ant4res, siempre que no haga referencia a la instancia de tarea asíncrona, el GC liberará la memoria. Sin embargo, si tiene una tarea en segundo plano en curso, podría considerar realizarla en un bucle dentro de doInBackground y hacer llamadas a PublishProgress para actualizar el progreso. O, otro enfoque sería colocar su tarea en un hilo de fondo. Aquí hay muchos enfoques diferentes, pero no puedo recomendar uno sobre otro sin detalles.
Steve Prentice
28

Las razones de las instancias de disparo y olvido de ASyncTask se detallan bastante bien en la respuesta de Steve Prentice. Sin embargo, aunque tiene restricciones sobre cuántas veces ejecuta ASyncTask, puede hacer lo que quiera mientras el hilo se está ejecutando. .

Coloque su código ejecutable dentro de un bucle dentro de doInBackground () y use un bloqueo concurrente para activar cada ejecución. Puede recuperar los resultados con PubliveProgress () / onProgressUpdate () .

Ejemplo:

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

Por supuesto, esto es un poco más complicado que el uso tradicional de ASyncTask, y abandonas el uso de PubliveProgress () para los informes de progreso reales. Pero si le preocupa la memoria, este enfoque garantizará que solo quede un ASyncTask en el montón en tiempo de ejecución.

seanhodges
fuente
Pero el punto es que no quiero volver a ejecutar el Asyntask mientras se está ejecutando, pero debido a que este ha finalizado y no ha recibido los datos como debería, vuelva a llamarlo.
Dayerman
En realidad, solo ejecuta ASyncTask una vez de esta manera, y puede verificar si los datos son correctos dentro del método onPublishProgress (o delegar la verificación en otro lugar). Utilicé este patrón para un problema similar hace un tiempo (muchas tareas se dispararon en rápida sucesión, arriesgando el tamaño del montón).
seanhodges
Pero, ¿qué pasa si en ese momento el servidor no responde y quiero volver a intentarlo 10 segundos después? El AsyncTask ya ha terminado, ¿verdad? Entonces tengo que volver a llamarlo
Dayerman
He agregado un código de ejemplo para describir lo que quiero decir. ASyncTask solo finalizará una vez que esté satisfecho con el resultado y llame a "terminateTask ()".
seanhodges
1
Si obtiene IllegalMonitorStateExceptionen runAgain(llamado por onProgressUpdate) ver esta respuesta: stackoverflow.com/a/42646476/2711811 . Sugiere (y funcionó para mí) que las signal()necesidades deben estar rodeadas por un lock/ unlock. Esto puede tener que ver con el momento de la publishProgressllamada onProgressUpdate.
Andy
2

Tuve el mismo problema. En mi caso, tengo una tarea que quiero hacer dentro onCreate()y fuera onResume(). Así que hice mi Asynctask estática y obtuve la instancia de ella. Ahora todavía tenemos el mismo problema.

Entonces, lo que hice en onPostExecute () es esto:

instance = null;

Teniendo en cuenta que compruebo en el método getInstance estático que mi instancia no es nula, de lo contrario la creo:

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

El método en postExecute vaciará la instancia y la recreará. Por supuesto, esto se puede hacer fuera de la clase.

SamuelD
fuente
1

He hecho que mis tareas de rotación sean estáticas, lo que me ayudó a adjuntarlas, separarlas y volver a conectarlas a los hilos de la interfaz de usuario en los cambios de rotación. Pero para volver a su pregunta, lo que hago es crear una bandera para ver si el hilo se está ejecutando. Cuando desee reiniciar el hilo, verifico si la tarea de rotación se está ejecutando, si es, brindo una advertencia. Si no es así, lo hago nulo y luego creo uno nuevo, que evitará el error que está viendo. Además, al completar con éxito, anulo la tarea de reconocimiento de rotación completada para que esté lista para continuar nuevamente.

Arnab C.
fuente
0

Sí, es cierto, el documento dice que solo se puede ejecutar un Asyntask.

Cada vez que necesite usarlo debe tener una instancia:

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