Обмеження потоків Android AsyncTask?


95

Я розробляю додаток, де мені потрібно оновлювати деяку інформацію кожного разу, коли користувач входить в систему, я також використовую базу даних у телефоні. Для всіх цих операцій (оновлення, отримання даних з db тощо) я використовую асинхронні завдання. Як і дотепер, я не розумів, чому я не повинен їх використовувати, але нещодавно я переконався, що якщо я роблю деякі операції, деякі мої асинхронні завдання просто зупиняються на попередньому виконанні і не переходять до doInBackground. Це було занадто дивно, щоб залишити це так, тому я розробив ще одне просте додаток, щоб просто перевірити, що не так. І як не дивно, я отримую однакову поведінку, коли кількість загальних асинхронних завдань досягає 5, а 6-е зупиняється на попередньому виконанні.

Чи має андроїд обмеження asyncTasks на Activity / App? Або це просто якась помилка, і про неї слід повідомити? Хтось стикався з тією ж проблемою і, можливо, знайшов її вирішення?

Ось код:

Просто створіть 5 із цих потоків для роботи у фоновому режимі:

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

А потім створіть цю нитку. Він увійде до preExecute і зависне (він не піде на 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");
    }
}

Відповіді:


207

Все AsyncTasks управляються внутрішньо спільно (статичний) ThreadPoolExecutor і LinkedBlockingQueue . Коли ви зателефонуєте executeдо AsyncTask, програма ThreadPoolExecutorвиконає її, коли вона буде готова деякий час у майбутньому.

"Коли я готовий?" поведінка a ThreadPoolExecutorконтролюється двома параметрами, розміром основного пулу та максимальним розміром пулу . Якщо нині активних потоків розміру пулу менше основного, і приходить нове завдання, виконавець створить новий потік і негайно його виконає. Якщо запущено щонайменше потоки розміру основного пулу, він спробує поставити завдання в чергу і почекати, поки не буде доступний потік, що не працює (тобто до завершення іншого завдання). Якщо неможливо поставити завдання в чергу (черга може мати максимальну ємність), він створить новий потік (до максимального розміру потоку пулу) для завдань, в яких будуть виконуватися. Неядерні простої потоків в кінцевому підсумку можуть бути виведені з експлуатації відповідно до параметра очікування часу збереження.

До Android 1.6 розмір основного пулу був 1, а максимальний - 10. Починаючи з Android 1.6, основний розмір пулу становив 5, а максимальний розмір пулу - 128. В обох випадках розмір черги - 10. Тайм-аут збереження часу був 10 секунд до 2.3, а з тих пір - 1 секунда.

З огляду на все це, тепер стає зрозумілим, чому AsyncTaskволевиявлення виконується лише на 5/6 ваших завдань. 6-те завдання перебуває в черзі, поки не виконає одне з інших завдань. Це дуже вагома причина, чому ви не повинні використовувати AsyncTasks для тривалих операцій - це заважатиме іншим AsyncTasks ніколи не працювати.

Для повноти, якщо ви повторили вправу з більш ніж 6 завданнями (наприклад, 30), ви побачите, що більше 6 увійде, doInBackgroundоскільки черга заповниться, і виконавець буде натиснуто на створення більше робочих потоків. Якщо ви продовжували виконувати довготривале завдання, ви побачите, що 20/30 стане активним, а 10 все ще в черзі.


2
"Це дуже вагома причина, чому ви не повинні використовувати AsyncTasks для тривалих операцій" Яка ваша рекомендація для цього сценарію? Нерест нового потоку вручну або створення власної служби виконавця?
користувач123321

2
Виконавці - це в основному абстракція поверх потоків, що полегшує необхідність писати складний код для управління ними. Це відокремлює ваші завдання від того, як їх слід виконувати. Якщо ваш код залежить лише від виконавця, то легко прозоро змінити кількість використовуваних потоків і т. Д. Я насправді не можу придумати поважної причини створювати теми самостійно, оскільки навіть для простих завдань обсяг роботи з виконавець - це те саме, якщо не менше.
antonyt

37
Зверніть увагу, що з Android 3.0+ кількість одночасних AsyncTasks за замовчуванням зменшена до 1. Більше інформації: developer.android.com/reference/android/os/…
Kieran

Ого, дякую за чудову відповідь. Нарешті, я маю пояснення, чому мій код зазнає невдач так епізодично і таємниче.
Chris Knight

@antonyt, Ще один сумнів, Скасовані AsyncTasks, чи буде це враховуватись до кількості AsyncTasks? тобто зараховується в core pool sizeабо maximum pool size?
nmxprime

9

@antonyt має правильну відповідь, але якщо ви шукаєте просте рішення, тоді ви можете перевірити Needle.

За допомогою нього ви можете визначити власний розмір пулу потоків, і, на відміну від цього AsyncTask, він працює на всіх версіях Android однаково. За допомогою нього ви можете говорити такі речі:

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

або подібні речі

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

Це може зробити навіть набагато більше. Перевірте це на GitHub .


остання
комісія

5

Оновлення : Починаючи з API 19, розмір пулу основних потоків був змінений, щоб відображати кількість процесорів на пристрої, принаймні 2 і максимум 4 на початку, при цьому збільшившись до максимуму процесора * 2 +1 - Довідка

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

Також зауважте, що хоча виконавець за замовчуванням AsyncTask є послідовним (виконує одне завдання за раз і в тому порядку, в якому вони надходять), з методом

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

Ви можете надати Виконавця для виконання Ваших завдань. Ви можете надати THREAD_POOL_EXECUTOR виконавця під капотом, але без серіалізації завдань, або ви навіть можете створити власного виконавця і надати його тут. Однак уважно зверніть увагу на попередження в Javadocs.

Попередження: Дозволити паралельному запуску кількох завдань із пулу потоків, як правило, не те, що хочеться, оскільки порядок їх роботи не визначений. Наприклад, якщо ці завдання використовуються для модифікації будь-якого загального стану (наприклад, написання файлу через клацання кнопки), немає жодних гарантій щодо порядку модифікацій. Без ретельної роботи в рідкісних випадках можливо, щоб новіша версія даних була перезаписана старшою, що призвело б до неясних втрат даних та проблем зі стабільністю. Такі зміни найкраще виконувати послідовно; щоб гарантувати серіалізацію такої роботи, незалежно від версії платформи, ви можете використовувати цю функцію з SERIAL_EXECUTOR.

Ще одне, на що слід звернути увагу, - це те, що як фреймворк, що надається виконавцями THREAD_POOL_EXECUTOR, так і його послідовна версія SERIAL_EXECUTOR (яка є типовою для AsyncTask) є статичними (конструкції рівня класу) і, отже, розподіляються між усіма екземплярами AsyncTask у процесі вашого додатка.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.