Ограничения потоков Android AsyncTask?

Я разрабатываю приложение, где мне нужно обновлять некоторую информацию каждый раз, когда пользователь входит в систему, я также использую базу данных в телефоне. Для всех этих операций (обновления, извлечение данных из db и т.д.) Я использую async-задачи. Поскольку до сих пор я не понимал, почему я не должен их использовать, но в последнее время я испытал, что, если я выполняю некоторые операции, некоторые из моих задач async просто останавливаются на предварительном выполнении и не переходят на doInBackground. Это было слишком странно, чтобы оставить это так, поэтому я разработал еще одно простое приложение, чтобы проверить, что случилось. И, как ни странно, я получаю такое же поведение, когда количество асинхронных задач достигает 5, 6-й - при предварительном выполнении.

Есть ли у android ограничения 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");
    }
}

Ответ 1

Все AsyncTasks управляются внутри общего (статического) ThreadPoolExecutor и LinkedBlockingQueue. Когда вы вызываете execute в AsyncTask, ThreadPoolExecutor выполнит его, когда он будет готов в будущем.

"Когда я готов?" поведение a ThreadPoolExecutor контролируется двумя параметрами: размером и максимальным размером пула. Если в настоящее время активны нити основного потока пула, а новое задание приходит, исполнитель создаст новый поток и выполнит его немедленно. Если есть хотя бы основные потоки размера пула, он попытается поставить в очередь задание и дождаться, пока не будет доступ к свободному потоку (т.е. Пока не будет выполнено другое задание). Если невозможно поставить в очередь задание (очередь может иметь максимальную емкость), он создаст новый поток (вплоть до максимальных потоков пула) для рабочих заданий, которые будут выполняться. Неядерные простаивающие потоки могут быть в конечном итоге выведены из эксплуатации в соответствии с параметром тайм-аута keep-alive.

Перед Android 1.6 размер основного пула был 1, а максимальный размер пула - 10. С Android 1.6 размер основного пула равен 5, а максимальный размер пула - 128. Размер очереди равен 10 в обоих случаях, Таймаут keep-alive составлял 10 секунд до 2,3 и 1 секунду с тех пор.

Учитывая все это, теперь становится ясным, почему AsyncTask будет только выполнять 5/6 ваших задач. 6-я задача ставится в очередь до завершения одной из других задач. Это очень хорошая причина, по которой вы не должны использовать AsyncTasks для длительных операций - это предотвратит запуск других AsyncTasks.

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

Ответ 2

@antonyt имеет правильный ответ, но если вы ищете простое решение, вы можете проверить иглу.

С его помощью вы можете определить собственный размер пула потоков и, в отличие от 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.

Ответ 3

Обновление: начиная с API 19 размер пула основных потоков был изменен, чтобы отражать количество процессоров на устройстве с минимальным значением 2 и максимум 4 при запуске, при увеличении до максимального значения CPU * 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.

Еще одна вещь, которую следует отметить, заключается в том, что обе структуры, предоставленные Executors THREAD_POOL_EXECUTOR и ее серийная версия SERIAL_EXECUTOR (которая по умолчанию для AsyncTask), являются статическими (конструкциями уровня класса) и, следовательно, совместно используются во всех экземплярах AsyncTask (ов) в вашем приложении.