Exécuter plusieurs AsyncTasks en même temps - pas possible?

259

J'essaie d'exécuter deux AsyncTasks en même temps. (La plate-forme est Android 1.5, HTC Hero.) Cependant, seul le premier est exécuté. Voici un simple extrait pour décrire mon problème:

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 sortie que j'attends est:

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

Etc. Cependant, ce que je reçois est:

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

La deuxième AsyncTask n'est jamais exécutée. Si je change l'ordre des instructions execute (), seule la tâche foo produira une sortie.

Suis-je en train de manquer quelque chose d'évident ici et / ou de faire quelque chose de stupide? N'est-il pas possible d'exécuter deux AsyncTasks en même temps?

Edit: j'ai réalisé que le téléphone en question fonctionne sous Android 1.5, j'ai mis à jour le problème. en conséquence. Je n'ai pas ce problème avec un HTC Hero fonctionnant sous Android 2.1. Hmmm ...

rodion
la source
Votre code fonctionne pour moi, donc le problème doit être ailleurs. Avez-vous entré un filtre dans votre vue LogCat? ;-)
mreichelt
Hm, c'est bizarre. Je n'ai aucun filtrage dans logcat. Utilisez-vous également 1.6? Si oui, quel téléphone?
rodion
Oups, je viens de réaliser qu'il fonctionne (ancien) Android 1.5
rodion
1
J'ai utilisé Android 1.6 comme cible et un émulateur Android 2.1. Donc, si le problème se produit vraiment sur un HTC Hero avec Android 1.5 uniquement - vissez-les, tout va bien. ;-) HTC Hero a déjà la mise à jour vers une nouvelle version d'Android. Je ne m'embêterais pas à ce sujet s'il y a des fabricants qui bousillent les choses. De plus, cela ne me dérangerait plus d'Android 1.5.
mreichelt
AsyncTask doit être utilisé pour des tâches de plus courte durée de 5 ms. Passez à ThreadPoolExecutor ( developer.android.com/reference/java/util/concurrent/… ). Article connexe: stackoverflow.com/questions/6964011/…
Ravindra babu

Réponses:

438

AsyncTask utilise un modèle de pool de threads pour exécuter les éléments à partir de doInBackground (). Le problème est initialement (dans les premières versions du système d'exploitation Android), la taille du pool n'était que de 1, ce qui signifie qu'il n'y a pas de calculs parallèles pour un tas d'AsyncTasks. Mais plus tard, ils ont corrigé cela et maintenant la taille est de 5, donc au plus 5 AsyncTasks peuvent s'exécuter simultanément. Malheureusement, je ne me rappelle pas dans quelle version exactement ils ont changé cela.

METTRE À JOUR:

Voici ce que l'API actuelle (2012-01-27) dit à ce sujet:

Lors de leur introduction, les AsyncTasks ont été exécutés en série sur un seul thread d'arrière-plan. À partir de DONUT, cela a été changé en un pool de threads permettant à plusieurs tâches de fonctionner en parallèle. Après HONEYCOMB, il est prévu de le remplacer par un seul thread pour éviter les erreurs d'application courantes causées par l'exécution parallèle. Si vous voulez vraiment une exécution parallèle, vous pouvez utiliser la version executeOnExecutor (Executor, Params ...) de cette méthode avec THREAD_POOL_EXECUTOR; cependant, voir les commentaires pour les avertissements sur son utilisation.

DONUT est Android 1.6, HONEYCOMB est Android 3.0.

MISE À JOUR: 2

Voir le commentaire de kabukode Mar 7 2012 at 1:27.

Il s'avère que pour les API où "un pool de threads permettant à plusieurs tâches d'opérer en parallèle" est utilisé (à partir de 1.6 et se terminant sur 3.0), le nombre d'AsyncTasks exécutés simultanément dépend du nombre de tâches qui ont déjà été passées pour exécution, mais n'ont pas encore terminé leur doInBackground().

Ceci est testé / confirmé par moi sur 2.2. Supposons que vous ayez une AsyncTask personnalisée qui ne dort qu'une seconde doInBackground(). Les AsyncTasks utilisent une file d'attente de taille fixe en interne pour stocker les tâches retardées. La taille de la file d'attente est 10 par défaut. Si vous lancez 15 vos tâches personnalisées d'affilée, les 5 premières entreront leur doInBackground(), mais le reste attendra dans une file d'attente un thread de travail gratuit. Dès que l'un des 5 premiers se termine et libère ainsi un thread de travail, une tâche de la file d'attente démarre l'exécution. Donc, dans ce cas, au plus 5 tâches seront exécutées simultanément. Cependant, si vous démarrez 16 vos tâches personnalisées d'affilée, les 5 premiers entreront leurs doInBackground(), les 10 autres entreront dans la file d'attente, mais pour le 16, un nouveau thread de travail sera créé afin qu'il démarre immédiatement. Donc, dans ce cas, au plus 6 tâches s'exécuteront simultanément.

Le nombre de tâches pouvant être exécutées simultanément est limité. Puisqu'il AsyncTaskutilise un exécuteur de pool de threads avec un nombre maximum limité de threads de travail (128) et que la file d'attente des tâches retardées a une taille fixe de 10, si vous essayez d'exécuter plus de 138 vos tâches personnalisées, l'application se bloquera java.util.concurrent.RejectedExecutionException.

Depuis la version 3.0, l'API permet d'utiliser votre exécuteur de pool de threads personnalisé via la AsyncTask.executeOnExecutor(Executor exec, Params... params)méthode. Cela permet, par exemple, de configurer la taille de la file d'attente des tâches retardées si la valeur par défaut 10 n'est pas celle dont vous avez besoin.

Comme le mentionne @Knossos, il existe une option à utiliser à AsyncTaskCompat.executeParallel(task, params);partir de la bibliothèque de support v.4 pour exécuter des tâches en parallèle sans se soucier du niveau de l'API. Cette méthode est devenue obsolète au niveau de l'API 26.0.0.

MISE À JOUR: 3

Voici une application de test simple pour jouer avec un certain nombre de tâches, exécution série ou parallèle: https://github.com/vitkhudenko/test_asynctask

MISE À JOUR: 4 (merci @penkzhou de l'avoir signalé)

À partir d'Android 4.4, le AsyncTaskcomportement diffère de celui décrit dans la section MISE À JOUR: 2 . Il existe un correctif pour éviter AsyncTaskde créer trop de threads.

Avant Android 4.4 (API 19) AsyncTaskavait les champs suivants:

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

Dans Android 4.4 (API 19), les champs ci-dessus sont modifiés comme suit:

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

Cette modification augmente la taille de la file d'attente à 128 éléments et réduit le nombre maximal de threads au nombre de cœurs de processeur * 2 + 1. Les applications peuvent toujours soumettre le même nombre de tâches.

Vit Khudenko
la source
11
Juste pour info, la taille du pool de base est de 5, mais max est de 128. Cela signifie que si vous remplissez votre file d'attente, plus de 5 (jusqu'à 128) peuvent s'exécuter simultanément.
Kabuko
2
@kabuko: vous avez absolument raison. Je viens d'enquêter sur 2.2 et de confirmer que si 5 tâches sont déjà en cours d'exécution (la taille du pool principal est de 5), il commence alors à placer le reste des tâches dans la file d'attente des tâches retardées, cependant si la file d'attente est déjà pleine (la taille maximale de la file d'attente est de 10 ), puis il démarre un thread de travail supplémentaire pour la tâche afin que la tâche démarre immédiatement.
Vit Khudenko
1
vient de sauver ma journée, Héros de la légende :) Merci
Karoly
2
Veuillez mettre à jour cette réponse pour les nouvelles fonctions de compatibilité . Exemple:AsyncTaskCompat.executeParallel(task, params);
Knossos
1
@Arhimed Veuillez mettre à jour la réponse car elle AsyncTaskCompat.executeParallel(task, params);est désormais obsolète
Kirill Starostin
218

Cela permet une exécution parallèle sur toutes les versions d'Android avec 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);
}

Ceci est un résumé de l'excellente réponse d'Arhimed.

Veuillez vous assurer d'utiliser l'API de niveau 11 ou supérieur comme cible de génération de projet. Dans Eclipse, c'est Project > Properties > Android > Project Build Target. Cela ne rompra pas la compatibilité descendante avec des niveaux d'API inférieurs. Ne vous inquiétez pas, vous obtiendrez des erreurs Lint si vous utilisez accidentellement des fonctionnalités introduites après minSdkVersion. Si vous voulez vraiment utiliser les fonctionnalités introduites après minSdkVersion, vous pouvez supprimer ces erreurs à l'aide d'annotations, mais dans ce cas, vous devez vous soucier de la compatibilité vous-même . C'est exactement ce qui s'est produit dans l'extrait de code ci-dessus.

sulai
la source
3
J'ai dû changer le TimerTask.THREAD_POOL_EXECUTOR en AsynTask.THREAD_POOL_EXECUTOR alors cet exemple fonctionne
Ronnie
2
Pour annuler les problèmes, vous pouvez rendre l'appel générique: void startMyTask(AsyncTask<Void, ?, ?> asyncTask) (si votre doInBackground prend Void)
Peter
Vous pouvez simplifier encore plus le code ci-dessus si votre classe étend AsyncTask <Void, Integer, Void>. Cela a très bien fonctionné sulai, merci.
Peter Arandorenko
1
Merci beaucoup. J'ai eu un scénario où je dois exécuter deux tâches asynchrones simultanément pour lire les données du serveur Web, mais j'ai trouvé que le serveur ne reçoit qu'une seule URL et tant que cette demande particulière n'est pas terminée, la deuxième URL d'AsyncTask ne frappera pas le serveur . Votre réponse a résolu mon problème :)
Santhosh Gutta
1
Je continue à recevoir des votes pour cela, merci. :) Mais j'ai l'impression que beaucoup de gens essaient d'utiliser AsyncTask pour le réseautage où d'excellentes bibliothèques comme Volley , Glide ou Swagger sont de meilleurs choix. Assurez-vous de les vérifier avant d'utiliser AsyncTask :)
sulai
21

Rendre la suggestion @sulai plus générique:

@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
la source
solution plus générale: AsyncTaskCompat.executeParallel (nouveau MyAsyncTask, myprams);
Vikash Kumar Verma
11

Juste pour inclure la dernière mise à jour (UPDATE 4) dans la réponse immaculée de @Arhimed dans le très bon résumé 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
la source
Oh wow, génial. Cela signifie qu'il .executeOnExecutor()est désormais redondant pour l'API> = KITKAT, oui?
Taslim Oseni
3

C'est possible. La version de mon appareil Android est 4.0.4 et android.os.Build.VERSION.SDK_INT est 15

J'ai 3 filateurs

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

Et j'ai aussi une classe Async-Tack.

Voici mon code de chargement 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();

Cela fonctionne parfaitement. Un à un mes filateurs se chargèrent. Je n'ai pas utilisé executeOnExecutor.

Voici ma classe de tâches Async

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
la source
2
comment voyez-vous même qu'ils ne sont pas exécutés de manière séquentielle?
behelit
@behelit Est-ce important quand ça marche? Veuillez lire la réponse acceptée pour plus de détails stackoverflow.com/a/4072832/2345900
Sajitha Rathnayake
2
Désolé je ne comprends pas. Si vous ne pouvez pas dire si vos asynchrones sont séquentielles ou vraiment asynchrones, cela importe. En outre, la réponse liée indique d'utiliser la méthode executeonexecutor, que vous n'utilisez pas réellement dans votre réponse. Désolé si j'ai raté quelque chose.
behelit
1
"Un par un mes filateurs chargés." Vous l'avez dit vous-même littéralement qu'il s'agit d'une exécution en série, et non d'une exécution parallèle. Chacun de nos AsyncTasks est ajouté à une file d'attente et traité séquentiellement. Cela ne répond pas à la question du PO.
Joshua Pinter
Ceci est une réponse très ancienne. Si cela ne fonctionne pas pour vous, veuillez vous référer à la réponse acceptée
Sajitha Rathnayake
3

si vous souhaitez exécuter des tâches en parallèle, vous devez appeler la méthode executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "your task name")après la version Android 3.0; mais cette méthode n'existe pas avant Android 3.0 et après 1.6 car elle s'exécute en parallèle par elle-même, donc je vous suggère de personnaliser votre propre classe AsyncTask dans votre projet, pour éviter de lever des exceptions dans différentes versions d'Android.

alpha
la source