web-dev-qa-db-fra.com

Android AsyncTask threads limites?

Je développe une application où je dois mettre à jour certaines informations chaque fois que l'utilisateur se connecte au système. J'utilise également la base de données du téléphone. Pour toutes ces opérations (mises à jour, récupération de données de la base de données, etc.), j'utilise des tâches asynchrones. Jusqu'à présent, je ne voyais pas pourquoi je ne devrais pas les utiliser, mais j'ai récemment constaté que si je faisais certaines opérations, certaines de mes tâches asynchrones s'arrêtaient simplement lors de la pré-exécution et ne sautaient pas à doInBackground. C'était trop étrange pour le laisser comme ça, alors j'ai développé une autre application simple juste pour vérifier ce qui ne va pas. Et assez étrange, j'obtiens le même comportement lorsque le nombre total de tâches asynchrones atteint 5, le 6ème arrête en pré-exécution.

Est-ce que Android a une limite de tâches asynchrones sur Activity/App? Ou s'agit-il simplement d'un bogue qui devrait être signalé? Quelqu'un at-il rencontré le même problème et a peut-être trouvé une solution de contournement?

Voici le code:

Créez simplement 5 de ces threads pour travailler en arrière-plan:

private class LongAsync extends AsyncTask<String, Void, String>
{
    @Override
    protected void onPreExecute()
    {
        Log.d("TestBug","onPreExecute");
        isRunning = true;
    }

    @Override
    protected String doInBackground(String... params)
    {
        Log.d("TestBug","doInBackground");
        while (isRunning)
        {

        }
        return null;
    }

    @Override
    protected void onPostExecute(String result)
    {
        Log.d("TestBug","onPostExecute");
    }
}

Et puis créez ce fil. Il entrera dans preExecute et se bloquera (il ne ira pas à doInBackground).

private class TestBug extends AsyncTask<String, Void, String>
{
    @Override
    protected void onPreExecute()
    {
        Log.d("TestBug","onPreExecute");

        waiting = new ProgressDialog(TestActivity.this);
        waiting.setMessage("Loading data");
        waiting.setIndeterminate(true);
        waiting.setCancelable(true);
        waiting.show();
    }

    @Override
    protected String doInBackground(String... params)
    {
        Log.d("TestBug","doInBackground");
        return null;
    }

    @Override
    protected void onPostExecute(String result)
    {
        waiting.cancel();
        Log.d("TestBug","onPostExecute");
    }
}
94
Mindaugas Svirskas

Toutes les tâches asynchrones sont contrôlées en interne par un partage (statique) ThreadPoolExecutor et un LinkedBlockingQueue . Lorsque vous appelez execute sur une tâche asynchrone, le ThreadPoolExecutor l'exécutera lorsqu'il sera prêt dans le futur.

Le 'quand suis-je prêt?' le comportement d'un ThreadPoolExecutor est contrôlé par deux paramètres, la taille du pool principal et le pool maximal taille . S'il y a moins de threads actifs que la taille du pool principal actuellement et qu'un nouveau travail arrive, l'exécuteur créera un nouveau thread et l'exécutera immédiatement. Si au moins des threads de taille de pool principal sont en cours d'exécution, le système essaiera de mettre le travail en file d'attente et d'attendre qu'un thread inactif soit disponible (c'est-à-dire jusqu'à ce qu'un autre travail soit terminé). S'il n'est pas possible de mettre le travail en file d'attente (la file peut avoir une capacité maximale), un nouveau thread sera créé (threads de la taille maximale du pool) pour que les travaux s'exécutent. Les threads inactifs non principaux peuvent éventuellement être mis hors service. selon un paramètre de délai de maintien en vie.

Avant Android 1.6, la taille du pool principal était de 1 et la taille maximale du pool était de 10. Depuis Android 1.6, la taille du pool principal est de 5, et la la taille de la réserve est de 128. La taille de la file d'attente est de 10. Dans les deux cas, le délai de maintien en vie était inférieur à 10 secondes et à une seconde depuis.

Avec tout cela à l'esprit, il devient maintenant clair pourquoi le AsyncTask n'apparaîtra que pour exécuter 5/6 de vos tâches. La 6ème tâche est mise en file d'attente jusqu'à la fin de l'une des autres tâches. C'est une très bonne raison pour laquelle vous ne devriez pas utiliser AsyncTasks pour des opérations de longue durée - cela empêchera d'autres AsyncTasks de s'exécuter.

Pour être complet, si vous répétez votre exercice avec plus de 6 tâches (par exemple 30), vous verrez que plus de 6 entreront doInBackground car la file d'attente sera pleine et l'exécutant sera poussé à créer davantage de threads de travail. Si vous avez conservé la tâche de longue durée, vous devriez voir que 20/30 devient actif, il en reste 10 dans la file d'attente.

206
antonyt

@antonyt a la bonne réponse, mais si vous cherchez une solution simple, vous pouvez essayer Needle.

Avec cela, vous pouvez définir une taille de pool de threads personnalisée et, contrairement à AsyncTask, il fonctionne sur toutes les versions Android de la même manière. . Avec cela, vous pouvez dire des choses comme:

Needle.onBackgroundThread().withThreadPoolSize(3).execute(new UiRelatedTask<Integer>() {
   @Override
   protected Integer doWork() {
       int result = 1+2;
       return result;
   }

   @Override
   protected void thenDoUiRelatedWork(Integer result) {
       mSomeTextView.setText("result: " + result);
   }
});

ou des choses comme

Needle.onMainThread().execute(new Runnable() {
   @Override
   public void run() {
       // e.g. change one of the views
   }
}); 

Il peut faire encore beaucoup plus. Vérifiez-le sur GitHub .

9
Zsolt Safrany

Mise à jour : Depuis l’API 19, la taille du pool de threads principaux a été modifiée pour refléter le nombre de CPU sur le périphérique, avec un minimum de 2 et un maximum de 4 à la fin. démarrer, tout en atteignant un maximum de CPU * 2 +1 - Référence

// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

Notez également que, bien que l'exécuteur par défaut d'AsyncTask soit en série (exécute une tâche à la fois et dans l'ordre dans lequel elles arrivent), avec la méthode

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params)

vous pouvez fournir un exécuteur pour exécuter vos tâches. Vous pouvez fournir à THREAD_POOL_EXECUTOR l'exécuteur sous le capot, mais sans sérialisation des tâches, ou vous pouvez même créer votre propre exécuteur et le fournir ici. Cependant, notez soigneusement l'avertissement dans les Javadocs.

Avertissement: permettre à plusieurs tâches de s'exécuter en parallèle à partir d'un pool de threads n'est généralement pas ce que l'on souhaite, car leur ordre de fonctionnement n'est pas défini. Par exemple, si ces tâches sont utilisées pour modifier un état commun (par exemple, l'écriture d'un fichier à cause d'un clic de bouton), il n'y a aucune garantie sur l'ordre des modifications. Sans travail minutieux, il est possible que, dans de rares cas, la version la plus récente des données soit écrasée par une version plus ancienne, ce qui entraîne des problèmes de perte de données et de stabilité obscurs. De tels changements sont mieux exécutés en série; pour garantir que ce travail est sérialisé quelle que soit la version de la plate-forme, vous pouvez utiliser cette fonction avec SERIAL_EXECUTOR.

Une autre chose à noter est que la structure fournie par les exécuteurs THREAD_POOL_EXECUTOR et sa version série SERIAL_EXECUTOR (qui est la valeur par défaut pour AsyncTask) sont statiques (constructions au niveau de la classe) et sont donc partagées entre toutes les instances de AsyncTask (s) dans le processus de votre application.

5
rnk