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

259

Estoy tratando de 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

Y así. 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 instrucciones execute (), 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é el problema descrito. en consecuencia. No tengo este problema con un HTC Hero con Android 2.1. Hmmm ...

rodion
fuente
Su código funciona para mí, por lo que el problema tiene que estar en otro lugar. ¿Ingresó un filtro en su vista LogCat? ;-)
mreichelt
Hm, eso es extraño. No tengo ningún filtro en logcat. ¿Estás usando 1.6 también? Si es así, ¿qué teléfono?
rodion 01 de
Vaya, me acabo de dar cuenta de que está ejecutando Android (antiguo) 1.5
rodion
1
Usé Android 1.6 como objetivo y un emulador de Android 2.1. Entonces, si el problema realmente ocurre en un HTC Hero con Android 1.5 solo: atorníllelo, está bien. ;-) HTC Hero ya tiene la actualización a una nueva versión de Android. No me molestaría si hay algunos fabricantes que arruinan las cosas. Además, ya no me importaría Android 1.5.
mreichelt
AsyncTask debe usarse para tareas de menor duración de 5 ms. Mover a ThreadPoolExecutor ( developer.android.com/reference/java/util/concurrent/… ). Publicación
Ravindra babu

Respuestas:

438

AsyncTask usa un patrón de grupo de subprocesos para ejecutar el material desde doInBackground (). El problema es 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 luego lo arreglaron y ahora el tamaño es 5, por lo que a lo sumo 5 AsyncTasks pueden ejecutarse simultáneamente. Desafortunadamente no recuerdo en qué versión cambiaron exactamente eso.

ACTUALIZAR:

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

Cuando se introdujo por primera vez, las AsyncTasks se ejecutaron en serie en un único subproceso en segundo plano. Comenzando con DONUT, esto se cambió a un grupo de subprocesos que permite que varias tareas funcionen en paralelo. Después de HONEYCOMB, se planea volver a cambiar esto a un solo subproceso para evitar errores de aplicación comunes causados ​​por la ejecución paralela. Si realmente desea una ejecución paralela, puede usar la versión executeOnExecutor (Executor, Params ...) de este método con THREAD_POOL_EXECUTOR; sin embargo, vea comentarios allí para advertencias sobre su uso.

DONUT es Android 1.6, HONEYCOMB es Android 3.0.

ACTUALIZACIÓN: 2

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

Resulta que para las API donde se usa "un grupo de subprocesos que permite que múltiples tareas operen en paralelo" (comenzando desde 1.6 y terminando en 3.0), el número de AsyncTasks que se ejecutan simultáneamente depende de cuántas tareas ya se hayan pasado para la ejecución, pero No he 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 doInBackground(). AsyncTasks utiliza una cola de tamaño fijo internamente para almacenar tareas retrasadas. El tamaño de la cola es 10 por defecto. Si comienza 15 sus tareas personalizadas en una fila, entonces las primeras 5 ingresarán sus doInBackground(), pero el resto esperará en una cola para un hilo de trabajo libre. Tan pronto como alguno de los primeros 5 finalice y, por lo tanto, libere un subproceso de trabajo, se iniciará una tarea de la cola. Entonces, en este caso, como máximo 5 tareas se ejecutarán simultáneamente. Sin embargo, si comienza 16 sus tareas personalizadas en una fila, luego las primeras 5 ingresarán a las suyas doInBackground(), las 10 restantes entrarán en la cola, pero para el 16 se creará un nuevo subproceso de trabajo para que comience la ejecución de inmediato. Entonces, 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 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 sus tareas personalizadas, la aplicación se bloqueará java.util.concurrent.RejectedExecutionException.

A partir de 3.0, la API permite utilizar su ejecutor de grupo de subprocesos personalizado a través del AsyncTask.executeOnExecutor(Executor exec, Params... params)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 @Knossos menciona, hay una opción para usar AsyncTaskCompat.executeParallel(task, params);desde la biblioteca de soporte v.4 para ejecutar tareas en paralelo sin molestarse con el nivel de API. Este método quedó en desuso 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 se AsyncTaskcomporta de manera diferente a lo que se describió en la sección ACTUALIZACIÓN: 2 . Hay una solución para evitar AsyncTaskcrear 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
fuente
11
Solo para su información, el tamaño de la agrupación central es 5, pero el máximo es 128. Esto significa que si llena su cola, más de 5 (hasta 128) pueden ejecutarse simultáneamente.
kabuko
2
@kabuko: tienes toda la razón. Acabo de investigar esto en 2.2 y confirmando que si ya se están ejecutando 5 tareas (el tamaño del grupo principal es 5), entonces comienza a poner el resto de tareas en la cola de tareas retrasadas, sin embargo, si la cola ya está llena (el tamaño máximo de la cola es 10 ), inicia un subproceso de trabajo adicional para la tarea, de modo que la tarea se inicia inmediatamente.
Vit Khudenko
1
acaba de guardar mi día, Leyenda héroe :) Gracias
Karoly
2
Actualice esta respuesta para las nuevas funciones de compatibilidad . Ejemplo:AsyncTaskCompat.executeParallel(task, params);
Knossos
1
@Arhimed Por favor, actualice la respuesta ya AsyncTaskCompat.executeParallel(task, params);que ahora está en desuso
Kirill Starostin
218

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 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 interrumpirá la compatibilidad con versiones anteriores para reducir los niveles de API. No se preocupe, recibirá errores de pelusa si usa accidentalmente funciones introducidas más tarde minSdkVersion. Si realmente desea utilizar las funciones introducidas más tarde 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.

sulai
fuente
3
Tuve que cambiar TimerTask.THREAD_POOL_EXECUTOR a AsynTask.THREAD_POOL_EXECUTOR, entonces este ejemplo funciona
Ronnie
2
Para anular problemas, puede hacer que la llamada sea genérica: void startMyTask(AsyncTask<Void, ?, ?> asyncTask) (si su doInBackground anula)
Peter
Puede simplificar el código anterior aún más si su clase extiende AsyncTask <Void, Integer, Void>. Funcionó muy bien, gracias.
Peter Arandorenko
1
Muchas gracias. Tuve un escenario en el que necesito ejecutar dos tareas asíncronas simultáneamente para leer datos del servidor web, pero descubrí que el servidor recibe solo una URL y siempre que esa solicitud en particular no se complete, la segunda URL de AsyncTask no llegará al servidor . Su respuesta solucionó mi problema :)
Santhosh Gutta
1
Sigo recibiendo votos a favor por esto, gracias. :) Pero tengo la impresión de que muchas personas intentan usar AsyncTask para redes donde excelentes bibliotecas como Volley , Glide o Swagger son mejores opciones. Asegúrese de verificar esto antes de usar AsyncTask :)
sulai
21

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
fuente
solución más general: AsyncTaskCompat.executeParallel (nuevo MyAsyncTask, myprams);
Vikash Kumar Verma
11

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);
    }
}
Ali Nem
fuente
Oh wow, genial Esto significa .executeOnExecutor()que ahora es redundante para API> = KITKAT, ¿sí?
Taslim Oseni
3

Es posible. La versión de mi dispositivo Android es 4.0.4 y android.os.Build.VERSION.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 la ruleta

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 utilicé 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();
        }
    }
}
Sajitha Rathnayake
fuente
2
¿Cómo ves que no se ejecutan secuencialmente?
Behelit
@behelit ¿Importa cuando está funcionando? Lea la respuesta aceptada para obtener más detalles stackoverflow.com/a/4072832/2345900
Sajitha Rathnayake el
2
Lo siento, no entiendo. Si no puede saber si sus asíncronos son secuenciales o realmente asíncronos, entonces es importante. Además, la respuesta vinculada dice utilizar el método executeonexecutor, que en realidad no está utilizando en su respuesta. Lo siento si me perdí algo.
Behelit
1
"Uno por uno mis hilanderos cargados". Literalmente, usted mismo dijo que se trata de una ejecución en serie, no una ejecución paralela. Cada una de nuestras AsyncTasks se agrega a una cola y se procesa secuencialmente. Esto no responde a la pregunta del OP.
Joshua Pinter
Esta es una respuesta muy antigua. Si esto no funciona para usted, consulte la respuesta aceptada
Sajitha Rathnayake
3

si desea ejecutar tareas paralelas, debe llamar al método executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "your task name")después de la versión de Android 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, así que le sugiero que personalice su propia clase AsyncTask en su proyecto, para evitar lanzar excepciones en diferentes versiones de Android.

alfa
fuente