Ejecutar varias AsyncTasks al mismo tiempo: ¿no es posible?

Resuelto rodion asked hace 54 años • 7 respuestas

Estoy intentando ejecutar dos AsyncTasks al mismo tiempo. (La plataforma es Android 1.5, HTC Hero). Sin embargo, solo se ejecuta el primero. Aquí hay un fragmento simple para describir mi problema:

public class AndroidJunk extends Activity {
 class PrinterTask extends AsyncTask<String, Void, Void> {
     protected Void doInBackground(String ... x) {
      while (true) {
       System.out.println(x[0]);
       try {
        Thread.sleep(1000);
       } catch (InterruptedException ie) {
        ie.printStackTrace();
       }
      }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        new PrinterTask().execute("bar bar bar");
        new PrinterTask().execute("foo foo foo");

        System.out.println("onCreate() is done.");
    }
}

El resultado que espero es:

onCreate() is done.
bar bar bar
foo foo foo
bar bar bar
foo foo foo

Etcétera. Sin embargo, lo que obtengo es:

onCreate() is done.
bar bar bar
bar bar bar
bar bar bar

La segunda AsyncTask nunca se ejecuta. Si cambio el orden de las declaraciones de ejecución (), solo la tarea foo producirá resultados.

¿Me estoy perdiendo algo obvio aquí y/o estoy haciendo algo estúpido? ¿No es posible ejecutar dos AsyncTasks al mismo tiempo?

Editar: me di cuenta de que el teléfono en cuestión ejecuta Android 1.5, actualicé la descripción del problema. respectivamente. No tengo este problema con un HTC Hero con Android 2.1. Mmm ...

rodion avatar Jan 01 '70 08:01 rodion
Aceptado

AsyncTask usa un patrón de grupo de subprocesos para ejecutar el material desde doInBackground(). El problema es que inicialmente (en las primeras versiones del sistema operativo Android) el tamaño del grupo era solo 1, lo que significa que no había cálculos paralelos para un montón de AsyncTasks. Pero luego arreglaron eso y ahora el tamaño es 5, por lo que como máximo se pueden ejecutar 5 AsyncTasks simultáneamente. Lamentablemente no recuerdo en qué versión exactamente cambiaron eso.

ACTUALIZAR:

Esto es lo que dice la API actual (2012-01-27) sobre esto:

Cuando se introdujeron por primera vez, las AsyncTasks se ejecutaban en serie en un único subproceso en segundo plano. A partir de DONUT, esto se cambió a un grupo de subprocesos que permite que múltiples tareas operen en paralelo. Después de HONEYCOMB, se planea volver a cambiar esto a un solo subproceso para evitar errores comunes de aplicación causados ​​por la ejecución paralela. Si realmente desea una ejecución paralela, puede usar la versión ejecutarOnExecutor(Executor, Params...) de este método con THREAD_POOL_EXECUTOR; sin embargo, consulte el comentario allí para conocer las advertencias sobre su uso.

DONUT es Android 1.6, HONEYCOMB es Android 3.0.

ACTUALIZACIÓN: 2

Ver el comentario de kabukode Mar 7 2012 at 1:27.

Resulta que para las API donde se usa "un grupo de subprocesos que permiten que múltiples tareas operen en paralelo" (a partir de 1.6 y terminando en 3.0), la cantidad de AsyncTasks que se ejecutan simultáneamente depende de cuántas tareas ya se han pasado para su ejecución, pero Aún no han terminado doInBackground().

Esto lo probé/confirmé en 2.2. Suponga que tiene una AsyncTask personalizada que solo duerme un segundo en doInBackground(). AsyncTasks utiliza internamente una cola de tamaño fijo para almacenar tareas retrasadas. El tamaño de la cola es 10 de forma predeterminada. Si inicia 15 tareas personalizadas seguidas, las primeras 5 ingresarán a sus tareas doInBackground(), pero el resto esperará en una cola hasta que llegue un hilo de trabajo libre. Tan pronto como cualquiera de los primeros 5 finalice y, por lo tanto, libere un subproceso de trabajo, una tarea de la cola comenzará a ejecutarse. Entonces, en este caso, se ejecutarán como máximo 5 tareas simultáneamente. Sin embargo, si inicia 16 tareas personalizadas seguidas, las primeras 5 ingresarán a sus tareas doInBackground(), las 10 restantes ingresarán a la cola, pero para la 16 se creará un nuevo subproceso de trabajo para que comience la ejecución de inmediato. Entonces, en este caso, se ejecutarán como máximo 6 tareas simultáneamente.

Existe un límite de cuántas tareas se pueden ejecutar simultáneamente. Dado que AsyncTaskutiliza un ejecutor de grupo de subprocesos con un número máximo limitado de subprocesos de trabajo (128) y la cola de tareas retrasadas tiene un tamaño fijo de 10, si intenta ejecutar más de 138 tareas personalizadas, la aplicación fallará java.util.concurrent.RejectedExecutionException.

A partir de 3.0, la API permite utilizar el ejecutor de su grupo de subprocesos personalizado mediante AsyncTask.executeOnExecutor(Executor exec, Params... params)un método. Esto permite, por ejemplo, configurar el tamaño de la cola de tareas retrasadas si el valor predeterminado 10 no es lo que necesita.

Como menciona @Knossos, existe una opción para usar AsyncTaskCompat.executeParallel(task, params);desde la biblioteca de soporte v.4 para ejecutar tareas en paralelo sin preocuparse por el nivel de API. Este método quedó obsoleto en el nivel de API 26.0.0.

ACTUALIZACIÓN: 3

Aquí hay una aplicación de prueba simple para jugar con varias tareas, ejecución en serie o en paralelo: https://github.com/vitkhudenko/test_asynctask

ACTUALIZACIÓN: 4 (gracias @penkzhou por señalar esto)

A partir de Android 4.4 AsyncTaskse comporta de manera diferente a lo descrito en la sección ACTUALIZACIÓN: 2 . Existe una solución para evitar AsyncTaskque se creen demasiados hilos.

Antes de Android 4.4 (API 19) AsyncTasktenía los siguientes campos:

private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(10);

En Android 4.4 (API 19), los campos anteriores se cambian a esto:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

Este cambio aumenta el tamaño de la cola a 128 elementos y reduce la cantidad máxima de subprocesos a la cantidad de núcleos de CPU * 2 + 1. Las aplicaciones aún pueden enviar la misma cantidad de tareas.

Vit Khudenko avatar Nov 01 '2010 20:11 Vit Khudenko

Esto permite la ejecución paralela en todas las versiones de Android con API 4+ (Android 1.6+):

@TargetApi(Build.VERSION_CODES.HONEYCOMB) // API 11
void startMyTask(AsyncTask asyncTask) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    else
        asyncTask.execute(params);
}

Este es un resumen de la excelente respuesta de Arhimed.

Asegúrese de utilizar el nivel API 11 o superior como objetivo de compilación de su proyecto. En Eclipse, eso es Project > Properties > Android > Project Build Target. Esto no interrumpirá la compatibilidad con versiones anteriores de niveles API más bajos. No se preocupe, obtendrá errores de Lint si utiliza accidentalmente funciones introducidas después del minSdkVersion. Si realmente desea utilizar funciones introducidas posteriormente minSdkVersion, puede suprimir esos errores mediante anotaciones, pero en ese caso, deberá ocuparse usted mismo de la compatibilidad . Esto es exactamente lo que sucedió en el fragmento de código anterior.

Actualización 2022

Tenga en cuenta que AsyncTasksquedó obsoleto hace bastante tiempo. Si usas Kotlin, se recomienda usar Kotlin Coroutines y si dependes del uso de Java en tu proyecto, una alternativa es usar Executorsdesde java.util.concurrent. (fuente)

sulai avatar Dec 10 '2012 11:12 sulai

Hacer que la sugerencia de @sulai sea más genérica:

@TargetApi(Build.VERSION_CODES.HONEYCOMB) // API 11
public static <T> void executeAsyncTask(AsyncTask<T, ?, ?> asyncTask, T... params) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    else
        asyncTask.execute(params);
}   
AsafK avatar Jan 30 '2014 13:01 AsafK

Solo para incluir la última actualización (ACTUALIZACIÓN 4) en la inmaculada respuesta de @Arhimed en el muy buen resumen de @sulai:

void doTheTask(AsyncTask task) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // Android 4.4 (API 19) and above
        // Parallel AsyncTasks are possible, with the thread-pool size dependent on device
        // hardware
        task.execute(params);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { // Android 3.0 to
        // Android 4.3
        // Parallel AsyncTasks are not possible unless using executeOnExecutor
        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    } else { // Below Android 3.0
        // Parallel AsyncTasks are possible, with fixed thread-pool size
        task.execute(params);
    }
}
Ali Nem avatar Mar 27 '2017 03:03 Ali Nem

El ejemplo de los desarrolladores de Android sobre la carga de mapas de bits de manera eficiente utiliza una tarea asíncrona personalizada (copiada de Jellybean) para que pueda usar ejecutarOnExecutor en apis inferiores a <11.

http://developer.android.com/training/displaying-bitmaps/index.html

Descargue el código y vaya al paquete de utilidades.

OriolJ avatar Jan 22 '2013 10:01 OriolJ