Ejecutar múltiples AsyncTasks al mismo tiempo - ¿no es posible?


Estoy tratando de ejecutar dos AsyncTasks al mismo tiempo. (La plataforma es Android 1.5, HTC Hero.) Sin embargo, solo el primero es ejecutado. 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.");
    }
}

La salida que espero es:

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

Y así sucesivamente. Sin embargo, lo que consigo 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 instrucciones execute (), solo la tarea foo producirá la salida.

¿Me estoy perdiendo algo obvio aquí y / o 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 se ejecuta Android 1.5, he actualizado el problema descr. consecuentemente. No tengo este problema con un HTC Hero con Android 2.1. Hmmm ...

Author: Ravindra babu, 2010-11-01

7 answers

AsyncTask usa un patrón de grupo de subprocesos para ejecutar las cosas 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 hay cálculos paralelos para un montón de AsyncTasks. Pero más tarde arreglaron eso y ahora el tamaño es 5, por lo que como máximo 5 AsyncTasks pueden ejecutarse simultáneamente. Desafortunadamente no recuerdo en qué versión exactamente cambiaron eso.

ACTUALIZACIÓN:

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

Cuando se introdujo por primera vez, las AsyncTasks se ejecutaban en serie en un solo hilo de fondo. Comenzando con DONUT, esto se cambió a un conjunto de hilos que permiten que múltiples tareas operen en paralelo. Después de HONEYCOMB, se planea cambiar esto de nuevo a un solo hilo para evitar errores comunes de aplicación causados por la ejecución en paralelo. Si realmente desea ejecución en paralelo, puede utilizar el executeOnExecutor (Executor, Params...) versión de este método con THREAD_POOL_EXECUTOR; sin embargo, ver comentario allí para advertencias sobre su uso.

DONUT es Android 1.6, HONEYCOMB es Android 3.0.

ACTUALIZAR: 2

Ver el comentario de kabuko de Mar 7 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 la 1.6 y terminando en la 3.0) el número de AsyncTasks que se ejecutan simultáneamente depende de cuántas tareas se han pasado para su ejecución ya, pero no han terminado su doInBackground() todavía.

Esto es probado/confirmado por mí en 2.2. Supongamos que tiene una AsyncTask personalizada que solo duerme un segundo en doInBackground(). Los AsyncTasks usan internamente una cola de tamaño fijo para almacenar tareas retrasadas. El tamaño de la cola es 10 por defecto. Si inicia 15 tareas personalizadas en una fila, entonces las primeras 5 ingresarán su doInBackground(), pero el resto esperará en una cola para un hilo de trabajo libre. Tan pronto como cualquiera de los primeros 5 termina, y por lo tanto libera un hilo de trabajo, una tarea desde la cola comenzará la ejecución. Así que en este caso, como máximo 5 tareas se ejecutarán simultáneamente. Sin embargo, si inicia 16 sus tareas personalizadas en una fila, entonces las primeras 5 ingresarán su doInBackground(), las 10 restantes entrarán en la cola, pero para la 16a se creará un nuevo hilo de trabajo para que comience la ejecución inmediatamente. Así que en este caso, como máximo 6 tareas se ejecutarán simultáneamente.

Hay un límite de cuántas tareas se pueden ejecutar simultáneamente. Dado que AsyncTask utiliza un ejecutor de grupo de subprocesos con número máximo 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 se bloqueará con java.util.concurrent.RejectedExecutionException.

A partir de la 3.0, la API permite usar su ejecutor de grupo de subprocesos personalizado a través del método AsyncTask.executeOnExecutor(Executor exec, Params... params). 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 @ Knossos menciona, hay una opción para usar AsyncTaskCompat.executeParallel(task, params); de la biblioteca support v. 4 para ejecutar tareas en paralelo sin molestarse con el nivel de API. Este método quedó obsoleto en el nivel de API 26.0.0.

ACTUALIZAR: 3

Aquí hay una aplicación de prueba simple para jugar con el número de tareas, serie vs. ejecución paralela: https://github.com/vitkhudenko/test_asynctask

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

A partir de Android 4.4 AsyncTask se comporta de manera diferente de lo que se describió en ACTUALIZACIÓN: 2 sección. Alli es una solución para evitar que AsyncTask cree demasiados hilos.

Antes de Android 4.4 (API 19) AsyncTask tení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 el número máximo de subprocesos al número de núcleos de CPU * 2 + 1. Las aplicaciones todavía pueden enviar el mismo número de tareas.

 410
Author: Vit Khudenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-19 19:14:11

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 usar el nivel de API 11 o superior como objetivo de compilación de su proyecto. En Eclipse, eso es Project > Properties > Android > Project Build Target. Esto no romperá la compatibilidad con versiones anteriores a niveles de API más bajos. No se preocupe, obtendrá errores de pelusa si accidentalmente utiliza funciones introducidas después de minSdkVersion. Si realmente quieres usar características introducidas después de minSdkVersion, puede suprimir esos errores mediante anotaciones, pero en ese caso, debe tener cuidado con la compatibilidad usted mismo. Esto es exactamente lo que sucedió en el fragmento de código anterior.

 200
Author: sulai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-14 10:45:35

Haciendo 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);
}   
 20
Author: AsafK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-30 13:23:45

Solo para incluir la última actualización (ACTUALIZACIÓN 4) en la respuesta inmaculada 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);
    }
}
 6
Author: Ali Nem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-27 03:35:06

El ejemplo de desarrolladores de Android de cargar mapas de bits usa eficientemente una asynctask personalizada (copiada de jellybean) para que pueda usar el executeOnExecutor en api inferiores a

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

Descargue el código y vaya al paquete util.

 4
Author: OriolJ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-22 10:12:25

Es posible. La versión de mi dispositivo Android es 4.0.4 y Android.operativo.Construir.VERSIÓN.SDK_INT es 15

Tengo 3 hilanderos

Spinner c_fruit=(Spinner) findViewById(R.id.fruits);
Spinner c_vegetable=(Spinner) findViewById(R.id.vegetables);
Spinner c_beverage=(Spinner) findViewById(R.id.beverages);

Y también tengo una clase Async-Tack.

Aquí está mi código de carga de spinner

RequestSend reqs_fruit = new RequestSend(this);
reqs_fruit.where="Get_fruit_List";
reqs_fruit.title="Loading fruit";
reqs_fruit.execute();

RequestSend reqs_vegetable = new RequestSend(this);
reqs_vegetable.where="Get_vegetable_List";
reqs_vegetable.title="Loading vegetable";
reqs_vegetable.execute();

RequestSend reqs_beverage = new RequestSend(this);
reqs_beverage.where="Get_beverage_List";
reqs_beverage.title="Loading beverage";
reqs_beverage.execute();

Esto está funcionando perfectamente. Uno por uno mis hilanderos cargados. No usé executeOnExecutor.

Aquí está mi clase Async-task

public class RequestSend  extends AsyncTask<String, String, String > {

    private ProgressDialog dialog = null;
    public Spinner spin;
    public String where;
    public String title;
    Context con;
    Activity activity;      
    String[] items;

    public RequestSend(Context activityContext) {
        con = activityContext;
        dialog = new ProgressDialog(activityContext);
        this.activity = activityContext;
    }

    @Override
    protected void onPostExecute(String result) {
        try {
            ArrayAdapter<String> adapter = new ArrayAdapter<String> (activity, android.R.layout.simple_spinner_item, items);       
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            spin.setAdapter(adapter);
        } catch (NullPointerException e) {
            Toast.makeText(activity, "Can not load list. Check your connection", Toast.LENGTH_LONG).show();
            e.printStackTrace();
        } catch (Exception e)  {
            Toast.makeText(activity, "Can not load list. Check your connection", Toast.LENGTH_LONG).show();
            e.printStackTrace();
        }
        super.onPostExecute(result);

        if (dialog != null)
            dialog.dismiss();   
    }

    protected void onPreExecute() {
        super.onPreExecute();
        dialog.setTitle(title);
        dialog.setMessage("Wait...");
        dialog.setCancelable(false); 
        dialog.show();
    }

    @Override
    protected String doInBackground(String... Strings) {
        try {
            Send_Request();
            } catch (NullPointerException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        return null;
    }

    public void Send_Request() throws JSONException {

        try {
            String DataSendingTo = "http://www.example.com/AppRequest/" + where;
            //HttpClient
            HttpClient httpClient = new DefaultHttpClient();
            //Post header
            HttpPost httpPost = new HttpPost(DataSendingTo);
            //Adding data
            List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);

            nameValuePairs.add(new BasicNameValuePair("authorized","001"));

            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
            // execute HTTP post request
            HttpResponse response = httpClient.execute(httpPost);

            BufferedReader reader;
            try {
                reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                StringBuilder builder = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    builder.append(line) ;
                }

                JSONTokener tokener = new JSONTokener(builder.toString());
                JSONArray finalResult = new JSONArray(tokener);
                items = new String[finalResult.length()]; 
                // looping through All details and store in public String array
                for(int i = 0; i < finalResult.length(); i++) {
                    JSONObject c = finalResult.getJSONObject(i);
                    items[i]=c.getString("data_name");
                }

            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 3
Author: Sajitha Nilan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-31 06:27:39

Si desea ejecutar tareas en paralelo,necesita llamar al método executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "your task name") después de la versión de Android de 3.0; pero este método no existe antes de Android 3.0 y después de 1.6 porque se ejecuta en paralelo por sí mismo, por lo que sugiero que personalice su propia clase AsyncTask en su proyecto,para evitar la excepción de tiro en diferentes versiones de Android.

 1
Author: alpha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-24 12:29:39