Cómo funciona AsyncTask en Android

Quiero saber cómo funciona AsyncTask internamente.

Sé que utiliza el Java Executor para realizar las operaciones, pero todavía algunas de las preguntas que no estoy entendiendo. Me gusta:

  1. ¿Cuántos AsyncTask se pueden iniciar a la vez en una aplicación de Android?
  2. Cuando comienzo 10 AsyncTask, ¿se ejecutarán todas las tareas simultáneamente o una por una?

He intentado con 75000 AsyncTask para probar el mismo. No tengo ningún problema y parece que todas las tareas se empujan a la pila y se ejecutará uno por uno.

También cuando comienzo 100000 AsyncTasks, comienzo a recibir OutOfMemoryError.

Entonces, ¿hay algún límite de no de AsyncTask que se puede ejecutar a la vez?

Nota: he probado estos en SDK 4.0

AsyncTask tiene una historia bastante larga.

Cuando apareció por primera vez en Cupcake (1.5) manejó las operaciones de fondo con un solo hilo adicional (uno por uno). En Donut (1.6) se cambió, de modo que se había empezado a utilizar un grupo de hilos. Y las operaciones podrían ser procesadas simultáneamente hasta que la piscina se había agotado. En tal caso las operaciones fueron en cola.

Dado que el comportamiento predeterminado de Honeycomb se cambia de nuevo al uso de un único subproceso de trabajo (procesamiento uno por uno). Pero el nuevo método ( executeOnExecutor ) se introduce para darle la posibilidad de ejecutar tareas simultáneas si lo desea (hay dos ejecutores estándar diferentes: SERIAL_EXECUTOR y THREAD_POOL_EXECUTOR ).

La forma en que las tareas se enlistan también depende de qué ejecutor utilice. En caso de una paralela que está restringido con un límite de 10 ( new LinkedBlockingQueue<Runnable>(10) ). En caso de una serie que no están limitados ( new ArrayDeque<Runnable>() ).

Así que la forma en que se procesan sus tareas depende de cómo las ejecute y de la versión de SDK en la que las ejecute. En cuanto a los límites de hilo, no estamos garantizados con ninguno, sin embargo, mirando el código fuente ICS, podemos decir que el número de hilos en la piscina puede variar en el rango de 5..128 .

Cuando se inicia 100000 con el método de execute predeterminado se utiliza el ejecutor en serie. Dado que las tareas que no se pueden procesar de inmediato están en cola, se obtiene OutOfMemoryError (miles de tareas se añaden a la cola de respaldo de matriz).

El número exacto de tareas que puede iniciar a la vez depende de la clase de memoria del dispositivo en el que se esté ejecutando y, de nuevo, del ejecutor que utilice.

Vamos a profundizar en el archivo Asynctask.java de Android para entenderlo desde la perspectiva de un diseñador y cómo ha implementado muy bien el patrón de diseño Half Sync-Half Async en él.

Al principio de la clase, pocas líneas de códigos son las siguientes:

  private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(10); /** * An {@link Executor} that can be used to execute tasks in parallel. */ public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); 

El primero es un ThreadFactory que es responsable de crear hilos de trabajo. La variable miembro de esta clase es el número de subprocesos creados hasta ahora. En el momento en que crea un hilo de trabajo, este número se incrementa en 1.

El siguiente es el BlockingQueue. Como usted sabe de la documentación de bloqueo de bloqueo de Java, en realidad proporciona una cola sincronizada segura de subprocesos que implementa la lógica FIFO.

El siguiente es un ejecutor de pool de subprocesos que es responsable de crear un grupo de subprocesos de trabajo que se pueden tomar como y cuando sea necesario para ejecutar diferentes tareas.

Si observamos las primeras líneas, sabremos que Android ha limitado el número máximo de subprocesos a 128 (como es evidente desde static static final int MAXIMUM_POOL_SIZE = 128).

Ahora la siguiente clase importante es SerialExecutor que se ha definido como sigue:

 private static class SerialExecutor implements Executor { final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } } 

Las siguientes dos funciones importantes en el Asynctask son:

 public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } 

y

 public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; } 

A medida que se hace evidente desde el código anterior podemos llamar a executeOnExecutor desde la función exec de Asynctask y en ese caso se necesita un ejecutor predeterminado. Si cavamos en el código fuente de Asynctask, veremos que este ejecutor predeterminado no es más que un ejecutor en serie, cuyo código se ha dado anteriormente.

Ahora vamos a ahondar en la clase SerialExecutor. En esta clase tenemos final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();.

Esto realmente funciona como un serializador de las diferentes solicitudes en diferentes subprocesos. Este es un ejemplo del patrón Half Synyn Half Async.

Ahora vamos a examinar cómo el ejecutor en serie hace esto. Por favor, eche un vistazo a la parte del código del SerialExecutor que está escrito como

  if (mActive == null) { scheduleNext(); } 

Por lo tanto, cuando se ejecuta ejecutar primero en Asynctask, este código se ejecuta en el subproceso principal (como mActive se inicializará a NULL) y por lo tanto nos llevará a la función scheduleNext (). La función ScheduleNext () se ha escrito de la siguiente manera:

 protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } 

Así que en la función schedulenext () inicializamos el mActive con el objeto Runnable que ya hemos insertado al final de la dequeue. Este objeto Runnable (que no es nada más que el mActive) entonces se ejecuta en un thread tomado del threadpool. En ese hilo, entonces se ejecuta el bloque "finally".

Ahora hay dos escenarios.

  1. Se ha creado otra instancia de Asynctask y llamamos al método execute en ella cuando se ejecuta la primera tarea.

  2. Ejecutar se llama por segunda vez en una misma instancia de Asynctask cuando se ejecuta la primera tarea.

Escenario I: si observamos la función de ejecución del Serial Executor, veremos que en realidad creamos un nuevo subproceso ejecutable (Say thread t) para procesar la tarea de fondo. Mira el siguiente código snippet-

  public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); 

Como se hace claro de la línea mTasks.offer(new Runnable) , cada llamada a la función de ejecución crea un nuevo subproceso de trabajo. Ahora probablemente usted es capaz de descubrir la similitud entre el patrón Half Sync – Half Async y el funcionamiento de SerialExecutor. Permítanme, sin embargo, aclarar las dudas. Al igual que la capa asíncrona del modelo Half Sync – Half Async, la

 mTasks.offer(new Runnable() { .... } 

Parte del código crea un nuevo subproceso en el momento en que se llama a la función de ejecución y lo empuja a la cola (las mTasks). Se realiza de forma absolutamente asincrónica, ya que al momento de insertar la tarea en la cola, la función devuelve. Y entonces el hilo de fondo ejecuta la tarea de una manera síncrona. Por lo tanto, es similar a la media sincronización – patrón Half Async. ¿Derecha?

Entonces dentro de ese hilo t, ejecutamos la función de ejecución del mActivo. Pero como está en el bloque try, finalmente se ejecutará sólo después de que la tarea de fondo termine en ese hilo. (Recuerde que ambos intentan y finalmente están sucediendo dentro del contexto de t). Dentro de finally block, cuando llamamos a la función scheduleNext, la mActive se convierte en NULL porque ya hemos vaciado la cola. Sin embargo, si se crea otra instancia del mismo Asynctask y llamamos a ejecutar en ellos, la función de ejecución de estos Asynctask no se ejecutará debido a la palabra clave de sincronización antes de ejecutar y también porque el SERIAL_EXECUTOR es una instancia estática (de ahí que todos los objetos De la misma clase comparten la misma instancia … es un ejemplo de bloqueo de nivel de clase) quiero decir que ninguna instancia de la misma clase Async puede evitar la tarea de fondo que se ejecuta en el subproceso t. E incluso si el hilo es interrumpido por algunos eventos, el bloque final que llama de nuevo la función scheduleNext () se encargará de it.what todo significa que habrá sólo un hilo activo que ejecuta la tarea. Este hilo puede no ser el mismo para diferentes tareas, pero sólo un hilo a la vez ejecutará la tarea. Por lo tanto, las tareas posteriores se ejecutarán una tras otra sólo cuando se complete la primera tarea. Es por eso que se llama SerialExecutor.

Escenario II: En este caso, obtendremos un error de excepción. Para entender por qué la función de ejecución no se puede llamar más de una vez en el mismo objeto Asynctask, eche un vistazo al siguiente fragmento de código tomado de la función executorOnExecute de Asynctask.java, especialmente en la parte mencionada a continuación:

  if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } 

AS del fragmento de código anterior se hace claro que si llamamos a ejecutar la función dos veces cuando una tarea está en el estado de ejecución lanza una IllegalStateException diciendo "No se puede ejecutar la tarea: la tarea ya se está ejecutando.

Si queremos que se ejecuten varias tareas paralelamente, necesitamos llamar al execOnExecutor pasando Asynctask.THREAD_POOL_EXECUTOR (o tal vez un THREAD_POOL definido por el usuario como el parámetro exec.

Usted puede leer mi discusión sobre Asynctask internals aquí.

AsyncTasks tiene una cola de tamaño fijo internamente para almacenar tareas retrasadas. El tamaño de la cola por defecto es 10. Por ejemplo, si inicia 15 sus tareas en una fila, los primeros 5 ingresarán su doInBackground() , pero el resto esperará en la cola para el hilo de trabajo libre. Como uno de los primeros 5 acabados, y así libera el hilo de trabajo, una tarea de la cola iniciará la ejecución. En este caso, en la mayoría de las 5 tareas se ejecutarán juntos.

Sí, hay un límite de cuántas tareas se pueden ejecutar ejecuta a la vez. Así AsyncTask utiliza el ejecutor de grupo de subprocesos con el número máximo limitado de los subprocesos de trabajo y la cola de tareas retrasadas utiliza el tamaño fijo 10. El número máximo de subprocesos de trabajo es 128. Si intenta ejecutar más de 138 tareas personalizadas, su aplicación lanzará la excepción RejectedExecutionException .

  1. ¿Cuántos AsyncTask se pueden iniciar a la vez en una aplicación de Android?

    AsyncTask está respaldado por un LinkedBlockingQueue con una capacidad de 10 (en ICS y pan de jengibre). Por lo que realmente depende de cuántas tareas que están tratando de iniciar y cuánto tiempo tardan en terminar – pero es definitivamente posible agotar la capacidad de la cola.

  2. Cuando comienzo 10 AsyncTask, ¿se ejecutarán todas las tareas simultáneamente o una por una?

    Una vez más, esto depende de la plataforma. El tamaño máximo de la agrupación es de 128 tanto en pan de jengibre como en ICS, pero el comportamiento * predeterminado * cambió entre 2,3 y 4,0 – de paralelo por defecto a serie. Si desea ejecutar en paralelo en ICS, debe llamar a [executeOnExecutor] [1] junto con THREAD_POOL_EXECUTOR

Trate de cambiar al ejecutor paralelo y lo spam con 75 000 tareas – la impl serial. Tiene un ArrayDeque interno que no tiene límite de capacidad superior (excepto OutOfMemoryExceptions ofc).

  • Cancelar la descarga de archivos con httpclient y asynctask
  • El tiempo de espera del lanzamiento ha expirado, ¡dando para arriba el bloqueo de la estela! Tiempo inactivo de actividad para HistoryRecord. ¿Es esto algo de qué preocuparse?
  • Cargar anuncio (adMob) en el subproceso de fondo
  • Retrofit y manejo centralizado de errores
  • Android cómo esperar a que termine el código antes de continuar
  • Cómo transmitir el contexto de Fragment a la interfaz
  • Programar tareas asíncronas múltiples en android
  • Iniciar la tarea async de onhandleintent
  • Devolución de datos de AsyncTask sin bloquear la interfaz de usuario
  • ¿Cuál es la ventaja de los cargadores sobre Asynctask en Android?
  • No funciona la notificación de Android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.