web-dev-qa-db-fra.com

Exécuter plusieurs tâches asynchrones en même temps - impossible?

J'essaie d'exécuter deux tâches asynchrones en même temps. (La plate-forme est Android 1.5, HTC Hero.) Toutefois, seul le premier est exécuté. Voici un extrait de code décrivant 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.");
    }
}

Le résultat attendu 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 seconde AsyncTask n'est jamais exécutée. Si je change l'ordre des instructions execute (), seule la tâche foo produira une sortie.

Est-ce que je manque quelque chose d'évident ici et/ou faire quelque chose de stupide? N'est-il pas possible d'exécuter deux tâches asynchrones en même temps?

Modifier: 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 sous Android 2.1. Hmmm ...

247
rodion

AsyncTask utilise un modèle de pool de threads pour l'exécution de la commande doInBackground (). Au début, le problème était (au début, Android versions de système d'exploitation) que la taille du pool était de 1, ce qui signifie qu'aucun calcul parallèle n'était effectué pour un tas de tâches asynchrones. Mais plus tard, ils ont résolu le problème et la taille est maintenant de 5, donc au plus 5 AsyncTasks peuvent être exécutés simultanément. Malheureusement, je ne me souviens plus dans quelle version ils ont changé cela.

UPDATE:

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

Lors de la première introduction, les AsyncTasks étaient exécutées en série sur un seul thread en arrière-plan. À partir de DONUT, cela a été remplacé par un pool de threads permettant à plusieurs tâches de fonctionner en parallèle. Après HONEYCOMB, il est prévu de revenir à un seul thread pour éviter les erreurs d’application courantes causées par l’exécution en 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 dans cet article pour des avertissements sur son utilisation.

DONUT est Android 1.6, HONEYCOMB est Android 3.0.

UPDATE: 2

Voir le commentaire de kabuko de Mar 7 at 1:27.

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

Ceci est testé/confirmé par moi sur 2.2. Supposons que vous ayez une tâche Async personnalisée qui ne dort qu'une seconde dans doInBackground(). AsyncTasks utilise une file d'attente de taille fixe en interne pour stocker les tâches différées. La taille de la file d'attente est de 10 par défaut. Si vous démarrez vos tâches personnalisées à la suite, les cinq premières entreront leur doInBackground(), mais le reste attendra dans une file d'attente un thread de travail libre. Dès que l'un des 5 premiers se termine, et libère ainsi un thread de travail, une tâche de la file commence son exécution. Donc, dans ce cas, 5 tâches au maximum seront exécutées simultanément. Cependant, si vous démarrez 16 vos tâches personnalisées à la suite, alors les 5 premières entreront leur doInBackground(), les 10 autres entreront dans la file d'attente, mais pour le 16e, un nouveau thread de travail sera créé pour qu'il puisse démarrer l'exécution. immédiatement. Donc, dans ce cas, 6 tâches au maximum seront exécutées simultanément.

Le nombre de tâches pouvant être exécutées simultanément est limité. Puisque AsyncTask utilise un exécuteur de pool de threads avec un nombre maximal limité de threads de travail (128) et que la file d'attente des tâches différées a une taille fixe de 10, si vous essayez d'exécuter plus de 138 tâches personnalisées, l'application se bloque avec Java.util.concurrent.RejectedExecutionException.

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

Comme @Knossos le mentionne, il existe une option permettant d'utiliser AsyncTaskCompat.executeParallel(task, params); 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 dans le niveau API 26.0.0.

UPDATE: 3

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

UPDATE: 4 (merci @penkzhou pour l'avoir signalé)

À partir de Android 4.4 _ AsyncTask se comporte différemment de ce qui était décrit dans la section UPDATE: 2 . There est un correctif pour empêcher AsyncTask de créer trop de threads.

Avant Android 4.4 (API 19), AsyncTask avait 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 remplacés par ceci:

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 envoyer le même nombre de tâches.

425
Vit Khudenko

Cela permet une exécution parallèle sur toutes les versions de 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.

Assurez-vous d'utiliser l'API de niveau 11 ou supérieur comme cible de génération de projet. Dans Eclipse, il s'agit de Project > Properties > Android > Project Build Target. Ceci pas brisera la rétrocompatibilité avec des niveaux d'API inférieurs. Ne vous inquiétez pas, vous obtiendrez des erreurs de Lint si vous utilisez accidentellement fonctionnalités introduites plus tard que minSdkVersion. Si vous voulez vraiment utiliser les fonctionnalités introduites après minSdkVersion, vous pouvez supprimer ces erreurs en utilisant des annotations, mais dans ce cas, vous devez vous préoccuper de la compatibilité vous-même . C'est exactement ce qui s'est passé dans l'extrait de code ci-dessus.

210
sulai

Rendre la suggestion de @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);
}   
20
AsafK

Juste pour inclure la dernière mise à jour (UPDATE 4) dans la réponse impeccable de @Arhimed au 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);
    }
}
7
Ali Nem

L'exemple de développeurs Android relatif au chargement de bitmaps utilise efficacement un asyncte personnalisé (copié à partir de jellybean) afin que vous puissiez utiliser executeOnExecutor dans des apis inférieurs à <11.

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

Téléchargez le code et allez au paquet util.

4
OriolJ

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

J'ai 3 fileuses

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 aussi j'ai 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 par un mes fileuses chargées. Je n'ai pas utilisateur executeOnExecutor.

Voici ma classe 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
Sajitha Rathnayake

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 Android version de 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, c’est pourquoi je vous suggère de personnaliser votre propre classe AsyncTask dans votre projet afin d’éviter les exceptions exceptionnelles dans Android. _ version.

2
alpha