Android AsyncTask Threads Grenzen?

95

Ich entwickle eine Anwendung, in der ich jedes Mal, wenn sich ein Benutzer beim System anmeldet, einige Informationen aktualisieren muss. Außerdem verwende ich die Datenbank im Telefon. Für all diese Vorgänge (Aktualisierungen, Abrufen von Daten aus der Datenbank usw.) verwende ich asynchrone Aufgaben. Bis jetzt habe ich nicht verstanden, warum ich sie nicht verwenden sollte, aber kürzlich habe ich festgestellt, dass einige meiner asynchronen Aufgaben bei einigen Vorgängen einfach vor der Ausführung angehalten werden und nicht zu doInBackground springen. Das war einfach zu seltsam, um es so zu belassen, also habe ich eine andere einfache Anwendung entwickelt, um zu überprüfen, was falsch ist. Und seltsamerweise bekomme ich das gleiche Verhalten, wenn die Anzahl der asynchronen Gesamtaufgaben 5 erreicht, die sechste stoppt bei der Vorausführung.

Hat Android ein Limit von asyncTasks für Aktivität / App? Oder ist es nur ein Fehler und sollte gemeldet werden? Hat jemand das gleiche Problem erlebt und vielleicht eine Problemumgehung gefunden?

Hier ist der Code:

Erstellen Sie einfach 5 dieser Threads, um im Hintergrund zu arbeiten:

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

Und dann erstelle diesen Thread. Es wird preExecute eingeben und hängen (es wird nicht zu doInBackground gehen).

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

Antworten:

207

Alle AsyncTasks werden intern von einem gemeinsam genutzten (statischen) ThreadPoolExecutor und einer LinkedBlockingQueue gesteuert . Wenn Sie executeeine AsyncTask aufrufen, ThreadPoolExecutorwird sie ausgeführt, wenn sie in Zukunft fertig ist.

Das "Wann bin ich bereit?" Das Verhalten von a ThreadPoolExecutorwird durch zwei Parameter gesteuert, die Kernpoolgröße und die maximale Poolgröße . Wenn derzeit weniger als Kernpool-Threads aktiv sind und ein neuer Job eingeht, erstellt der Executor einen neuen Thread und führt ihn sofort aus. Wenn mindestens Threads mit Kernpoolgröße ausgeführt werden, wird versucht, den Job in die Warteschlange zu stellen und zu warten, bis ein inaktiver Thread verfügbar ist (dh bis ein anderer Job abgeschlossen ist). Wenn es nicht möglich ist, den Job in die Warteschlange zu stellen (die Warteschlange kann eine maximale Kapazität haben), wird ein neuer Thread (Threads mit maximaler Poolgröße) erstellt, in dem die Jobs ausgeführt werden können. Nicht zum Kern gehörende Threads können eventuell außer Betrieb genommen werden gemäß einem Keep-Alive-Timeout-Parameter.

Vor Android 1.6 betrug die Kernpoolgröße 1 und die maximale Poolgröße 10. Seit Android 1.6 beträgt die Kernpoolgröße 5 und die maximale Poolgröße 128. Die Größe der Warteschlange beträgt in beiden Fällen 10. Das Keep-Alive-Timeout betrug 10 Sekunden vor 2.3 und seitdem 1 Sekunde.

In Anbetracht dessen wird jetzt klar, warum der AsyncTaskscheinbar nur 5/6 Ihrer Aufgaben ausführt. Die 6. Aufgabe wird in die Warteschlange gestellt, bis eine der anderen Aufgaben abgeschlossen ist. Dies ist ein sehr guter Grund, warum Sie AsyncTasks nicht für lang laufende Vorgänge verwenden sollten. Dadurch wird verhindert, dass andere AsyncTasks jemals ausgeführt werden.

Wenn Sie Ihre Übung der Vollständigkeit halber mit mehr als 6 Aufgaben (z. B. 30) wiederholt haben, werden Sie feststellen, dass mehr als 6 Aufgaben eingegeben werden, doInBackgroundwenn die Warteschlange voll wird und der Executor dazu gedrängt wird, mehr Arbeitsthreads zu erstellen. Wenn Sie die lange laufende Aufgabe beibehalten haben, sollten Sie sehen, dass 20/30 aktiv wird und 10 noch in der Warteschlange stehen.

antonyt
quelle
2
"Dies ist ein sehr guter Grund, warum Sie AsyncTasks nicht für langfristige Vorgänge verwenden sollten." Was empfehlen Sie für dieses Szenario? Manuelles Erstellen eines neuen Threads oder Erstellen eines eigenen Executor-Dienstes?
user123321
2
Executoren sind im Grunde eine Abstraktion über Threads, wodurch die Notwendigkeit verringert wird, komplexen Code zu schreiben, um sie zu verwalten. Es entkoppelt Ihre Aufgaben von der Art und Weise, wie sie ausgeführt werden sollen. Wenn Ihr Code nur von einem Executor abhängt, ist es einfach, transparent zu ändern, wie viele Threads verwendet werden usw. Ich kann mir keinen guten Grund vorstellen, Threads selbst zu erstellen, da selbst für einfache Aufgaben der Arbeitsaufwand mit Ein Executor ist derselbe, wenn nicht weniger.
Antonyt
37
Bitte beachten Sie, dass ab Android 3.0+ die Standardanzahl gleichzeitiger AsyncTasks auf 1 reduziert wurde. Weitere Informationen: developer.android.com/reference/android/os/…
Kieran
Wow, vielen Dank für eine tolle Antwort. Schließlich habe ich eine Erklärung, warum mein Code so sporadisch und mysteriös versagt.
Chris Knight
@antonyt, Noch ein Zweifel, Abgebrochene AsyncTasks, zählt es zur Anzahl der AsyncTasks? dh gezählt in core pool sizeoder maximum pool size?
nmxprime
9

@antonyt hat die richtige Antwort, aber wenn Sie nach einer einfachen Lösung suchen, können Sie sich Needle ansehen.

Damit können Sie eine benutzerdefinierte Thread-Pool-Größe definieren, die im Gegensatz AsyncTaskzu allen Android-Versionen gleich funktioniert . Damit können Sie Dinge sagen wie:

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

oder Dinge wie

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

Es kann noch viel mehr. Schau es dir auf GitHub an .

Zsolt Safrany
quelle
Das letzte Commit war vor 5 Jahren :(
LukaszTaraszka
5

Update : Seit API 19 wurde die Größe des Kernthreadpools geändert, um die Anzahl der CPUs auf dem Gerät widerzuspiegeln, wobei zu Beginn mindestens 2 und höchstens 4 CPUs vorhanden sind, während die CPU * 2 +1 - Referenz auf maximal 2,5 erhöht wurde

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

Beachten Sie auch, dass der Standard-Executor von AsyncTask seriell ist (führt jeweils eine Aufgabe und in der Reihenfolge aus, in der sie ankommen), mit der Methode

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

Sie können einen Executor bereitstellen, um Ihre Aufgaben auszuführen. Sie können den THREAD_POOL_EXECUTOR als Executor unter der Haube bereitstellen, jedoch ohne Serialisierung von Aufgaben, oder Sie können sogar Ihren eigenen Executor erstellen und hier bereitstellen. Beachten Sie jedoch sorgfältig die Warnung in den Javadocs.

Warnung: Das parallele Ausführen mehrerer Aufgaben aus einem Thread-Pool ist im Allgemeinen nicht das, was man möchte, da die Reihenfolge ihrer Operation nicht definiert ist. Wenn diese Aufgaben beispielsweise verwendet werden, um einen gemeinsamen Status zu ändern (z. B. das Schreiben einer Datei aufgrund eines Klickens auf eine Schaltfläche), gibt es keine Garantie für die Reihenfolge der Änderungen. Ohne sorgfältige Arbeit ist es in seltenen Fällen möglich, dass die neuere Version der Daten von einer älteren Version überschrieben wird, was zu unklaren Datenverlust- und Stabilitätsproblemen führt. Solche Änderungen werden am besten seriell ausgeführt. Um sicherzustellen, dass solche Arbeiten unabhängig von der Plattformversion serialisiert werden, können Sie diese Funktion mit SERIAL_EXECUTOR verwenden.

Beachten Sie außerdem, dass sowohl das von Executors bereitgestellte Framework THREAD_POOL_EXECUTOR als auch die serielle Version SERIAL_EXECUTOR (standardmäßig für AsyncTask) statisch sind (Konstrukte auf Klassenebene) und daher für alle Instanzen von AsyncTask (s) in Ihrem App-Prozess gemeinsam genutzt werden.

rnk
quelle