Android AsyncTask threads limits?

Rozwijam aplikację, w której muszę aktualizować niektóre informacje za każdym razem, gdy użytkownik loguje się do systemu, korzystam również z bazy danych w telefonie. Dla wszystkich tych operacji (aktualizacje, pobieranie danych z db itd.) Używam zadań asynchronicznych. Jak do tej pory nie widziałem, dlaczego nie powinienem ich używać, ale ostatnio doświadczyłem, że jeśli wykonam kilka operacji, niektóre z moich zadań asynchronicznych po prostu zatrzymują się na pre-execute i nie przeskakują do doInBackground. To było zbyt dziwne, żeby to tak zostawić, więc rozwinąłem kolejna prosta aplikacja, aby sprawdzić, co jest nie tak. I dość dziwne, mam to samo zachowanie, gdy liczba wszystkich zadań asynchronicznych osiągnie 5, szósty zatrzymuje się na Pre-execute.

Czy android ma limit asynchronicznych zadań na aktywności / aplikacji? A może to tylko jakiś bug i należy go zgłosić? Czy ktoś doświadczył tego samego problemu i może znalazł obejście tego problemu?

Oto kod:

Po prostu utwórz 5 z tych wątków, aby działały w tle:

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

A następnie utworzyć ten wątek. Wejdzie w preExecute i zawiśnie (nie przejdzie do 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");
    }
}
Author: Mindaugas Svirskas, 2012-03-11

3 answers

Wszystkie asynchroniczne zadania są kontrolowane wewnętrznie przez wspólny (statyczny) ThreadPoolExecutor i LinkedBlockingQueue. Gdy wywołasz execute Na Asynctasku, ThreadPoolExecutor wykona ją, gdy będzie gotowa w przyszłości.

Kiedy będę gotowy ?'zachowanie {[1] } jest kontrolowane przez dwa parametry, rozmiar puli podstawowej i maksymalny rozmiar puli. Jeśli aktualnie aktywnych jest mniej niż rozmiar puli rdzeni i pojawia się nowe zadanie, executor utworzy nowy wątek i natychmiast go uruchomi. Jeśli uruchomione są co najmniej podstawowe wątki o rozmiarze puli, spróbuje ustawić zadanie w kolejce i poczekać, aż będzie dostępny bezczynny wątek (tzn. do zakończenia kolejnego zadania). Jeśli kolejkowanie zadania nie jest możliwe (kolejka może mieć maksymalną pojemność), utworzy nowy wątek (maksymalnie do maksymalnej puli wątków), w którym będą uruchamiane zadania. Niezrdzeniowe bezczynne wątki mogą być ostatecznie wycofane zgodnie z limitem czasu utrzymania parametr.

Przed Androidem 1.6 rozmiar puli rdzenia wynosił 1, A Maksymalny rozmiar puli wynosił 10. Od Androida 1.6 rozmiar puli rdzenia wynosi 5, a maksymalny rozmiar Puli wynosi 128. Wielkość kolejki w obu przypadkach wynosi 10. Czas utrzymywania przy życiu wynosił 10 sekund przed 2.3 i 1 sekundę od tego czasu.

Mając to wszystko na uwadze, teraz staje się jasne, dlaczego AsyncTask pojawi się tylko do wykonania 5/6 Twoich zadań. Szóste zadanie jest ustawiane w kolejce do ukończenia jednego z pozostałych zadań. To jest bardzo dobry powód, dla którego nie powinieneś używać asynchronicznych zadań do długotrwałych operacji - uniemożliwi to działanie innych asynchronicznych Zadań.

Dla kompletności, jeśli powtórzysz ćwiczenie z więcej niż 6 zadaniami (np. 30), zobaczysz, że więcej niż 6 wejdzie doInBackground, gdy kolejka stanie się pełna i executor zostanie wypchnięty, aby utworzyć więcej wątków roboczych. Jeśli utrzymałeś zadanie długo działające, powinieneś zobaczyć, że 20/30 staje się aktywne, a 10 nadal jest w kolejce.

 199
Author: antonyt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-06 18:02:14

@antonit ma prawidłową odpowiedź, ale jeśli szukasz prostego rozwiązania, możesz sprawdzić igłę.

Za jego pomocą można zdefiniować niestandardowy rozmiar puli wątków i, w przeciwieństwie do AsyncTask, działa on na wszystkich wersjach Androida tak samo. Z nim można powiedzieć takie rzeczy jak:

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

Lub rzeczy podobne

Needle.onMainThread().execute(new Runnable() {
   @Override
   public void run() {
       // e.g. change one of the views
   }
}); 
Może zrobić nawet o wiele więcej. Sprawdź to na GitHub.
 9
Author: Zsolt Safrany,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-29 23:11:24

Update: od API 19 rozmiar puli wątków rdzenia został zmieniony, aby odzwierciedlić liczbę procesorów na urządzeniu, z minimum 2 i maksymalnie 4 na początku, przy jednoczesnym wzroście do maksymalnej liczby procesorów*2 +1 - odniesienie

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

Należy również zauważyć, że podczas gdy domyślny wykonawca AsyncTask jest szeregowy (wykonuje jedno zadanie na raz i w kolejności, w jakiej przybywa), za pomocą metody

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

Możesz podać Wykonawcę do wykonywania swoich zadań. Możesz podać THREAD_POOL_EXECUTOR pod maską executor, ale bez serializacji zadań, lub możesz nawet utworzyć własny Executor i dostarczyć go tutaj. Zwróć jednak uwagę na ostrzeżenie w Javadocs.

Ostrzeżenie: pozwolenie na równoległe uruchamianie wielu zadań z puli wątków nie jest na ogół tym, czego się chce, ponieważ kolejność ich działania nie jest określona. Na przykład, jeśli te zadania są używane do modyfikowania dowolnego wspólnego stanu (takiego jak zapisanie pliku za pomocą kliknięcia przycisku), tam nie gwarantują kolejności modyfikacji. Bez starannej pracy w rzadkich przypadkach możliwe jest, że nowsza wersja danych zostanie nadpisana przez starszą, co prowadzi do niejasnych problemów z utratą danych i stabilnością. Takie zmiany są najlepiej wykonywane w trybie szeregowym; aby zagwarantować, że taka praca jest serializowana niezależnie od wersji platformy, możesz użyć tej funkcji z SERIAL_EXECUTOR.

Jeszcze jedną rzeczą do zauważenia jest to, że zarówno Framework dostarczał Executorów THREAD_POOL_EXECUTOR, jak i jego wersja szeregowa SERIAL_EXECUTOR (która jest domyślna dla AsyncTask) są statyczne(konstrukcje na poziomie klasy) i dlatego są współdzielone przez wszystkie instancje AsyncTask w procesie aplikacji.

 5
Author: rnk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-26 22:21:44