Mehrere AsyncTasks gleichzeitig ausführen - nicht möglich?

259

Ich versuche, zwei AsyncTasks gleichzeitig auszuführen. (Plattform ist Android 1.5, HTC Hero.) Es wird jedoch nur die erste ausgeführt. Hier ist ein einfacher Ausschnitt, um mein Problem zu beschreiben:

public class AndroidJunk extends Activity {
 class PrinterTask extends AsyncTask<String, Void, Void> {
     protected Void doInBackground(String ... x) {
      while (true) {
       System.out.println(x[0]);
       try {
        Thread.sleep(1000);
       } catch (InterruptedException ie) {
        ie.printStackTrace();
       }
      }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        new PrinterTask().execute("bar bar bar");
        new PrinterTask().execute("foo foo foo");

        System.out.println("onCreate() is done.");
    }
}

Die Ausgabe, die ich erwarte, ist:

onCreate() is done.
bar bar bar
foo foo foo
bar bar bar
foo foo foo

Und so weiter. Was ich jedoch bekomme, ist:

onCreate() is done.
bar bar bar
bar bar bar
bar bar bar

Die zweite AsyncTask wird nie ausgeführt. Wenn ich die Reihenfolge der Anweisungen execute () ändere, wird nur die Aufgabe foo ausgegeben.

Vermisse ich hier etwas Offensichtliches und / oder mache ich etwas Dummes? Ist es nicht möglich, zwei AsyncTasks gleichzeitig auszuführen?

Bearbeiten: Ich habe festgestellt, dass auf dem betreffenden Telefon Android 1.5 ausgeführt wird. Ich habe die Problembeschreibung aktualisiert. entsprechend. Ich habe dieses Problem nicht mit einem HTC Hero unter Android 2.1. Hmmm ...

rodion
quelle
Ihr Code funktioniert für mich, daher muss das Problem woanders liegen. Haben Sie in Ihrer LogCat-Ansicht einen Filter eingegeben? ;-)
mreichelt
Hm, das ist seltsam. Ich habe keine Filterung in Logcat. Verwenden Sie auch 1.6? Wenn ja, welches Telefon?
Rodion
Hoppla, habe gerade gemerkt, dass es (altes) Android 1.5
Rodion
1
Ich habe Android 1.6 als Ziel und einen Android 2.1-Emulator verwendet. Wenn das Problem also nur auf einem HTC Hero mit Android 1.5 auftritt - schrauben Sie sie, es geht Ihnen gut. ;-) HTC Hero hat bereits das Update auf eine neuere Android-Version. Ich würde mich nicht darum kümmern, wenn es einige Hersteller gibt, die es vermasseln. Außerdem würde mir Android 1.5 nichts mehr ausmachen.
mreichelt
AsyncTask sollte für Aufgaben mit kürzerer Dauer von 5 ms verwendet werden. Wechseln Sie zu ThreadPoolExecutor ( developer.android.com/reference/java/util/concurrent/… ). In Verbindung stehender Beitrag: stackoverflow.com/questions/6964011/…
Ravindra babu

Antworten:

438

AsyncTask verwendet ein Thread-Pool-Muster zum Ausführen des Materials von doInBackground (). Das Problem ist zunächst (in frühen Android-Betriebssystemversionen), dass die Poolgröße nur 1 betrug, was bedeutet, dass für eine Reihe von AsyncTasks keine parallelen Berechnungen durchgeführt wurden. Aber später haben sie das behoben und jetzt ist die Größe 5, so dass höchstens 5 AsyncTasks gleichzeitig ausgeführt werden können. Leider kann ich mich nicht erinnern, in welcher Version sie das genau geändert haben.

AKTUALISIEREN:

Hier ist, was die aktuelle (2012-01-27) API dazu sagt:

Bei der ersten Einführung wurden AsyncTasks seriell auf einem einzelnen Hintergrundthread ausgeführt. Beginnend mit DONUT wurde dies in einen Thread-Pool geändert, sodass mehrere Aufgaben gleichzeitig ausgeführt werden können. Nach HONEYCOMB ist geplant, dies wieder in einen einzelnen Thread zu ändern, um häufige Anwendungsfehler zu vermeiden, die durch parallele Ausführung verursacht werden. Wenn Sie wirklich eine parallele Ausführung wünschen, können Sie die Version executeOnExecutor (Executor, Params ...) dieser Methode mit THREAD_POOL_EXECUTOR verwenden. Im Kommentar finden Sie jedoch Warnungen zu seiner Verwendung.

DONUT ist Android 1.6, HONEYCOMB ist Android 3.0.

UPDATE: 2

Siehe den Kommentar von kabukovon Mar 7 2012 at 1:27.

Es stellt sich heraus, dass für APIs, bei denen "ein Pool von Threads verwendet wird, bei denen mehrere Aufgaben parallel ausgeführt werden können" (beginnend mit 1.6 und endend mit 3.0), die Anzahl der gleichzeitig ausgeführten AsyncTasks davon abhängt, wie viele Aufgaben bereits zur Ausführung übergeben wurden habe ihre doInBackground()noch nicht beendet .

Dies wird von mir am 2.2 getestet / bestätigt. Angenommen, Sie haben eine benutzerdefinierte AsyncTask, die nur eine Sekunde lang schläft doInBackground(). AsyncTasks verwenden intern eine Warteschlange mit fester Größe zum Speichern verzögerter Aufgaben. Die Warteschlangengröße beträgt standardmäßig 10. Wenn Sie 15 Ihrer benutzerdefinierten Aufgaben hintereinander starten, geben die ersten 5 ihre ein doInBackground(), während der Rest in einer Warteschlange auf einen freien Arbeitsthread wartet. Sobald eine der ersten 5 beendet ist und somit ein Arbeitsthread freigegeben wird, beginnt die Ausführung einer Aufgabe aus der Warteschlange. In diesem Fall werden also höchstens 5 Aufgaben gleichzeitig ausgeführt. Wenn Sie jedoch 16 Ihre benutzerdefinierten Aufgaben hintereinander starten, werden zuerst 5 eingegeben doInBackground(), die restlichen 10 werden in die Warteschlange gestellt. Für den 16. wird jedoch ein neuer Arbeitsthread erstellt, sodass die Ausführung sofort gestartet wird. In diesem Fall werden also höchstens 6 Aufgaben gleichzeitig ausgeführt.

Es gibt eine Begrenzung, wie viele Aufgaben gleichzeitig ausgeführt werden können. Da AsyncTaskein Thread-Pool-Executor mit einer begrenzten maximalen Anzahl von Worker-Threads (128) verwendet wird und die Warteschlange für verzögerte Aufgaben die feste Größe 10 hat, stürzt die App ab, wenn Sie versuchen, mehr als 138 Ihrer benutzerdefinierten Aufgaben auszuführen java.util.concurrent.RejectedExecutionException.

Ab 3.0 ermöglicht die API die Verwendung Ihres benutzerdefinierten Thread-Pool-Executors über die AsyncTask.executeOnExecutor(Executor exec, Params... params)Methode. Auf diese Weise können Sie beispielsweise die Größe der Warteschlange für verzögerte Aufgaben konfigurieren, wenn Sie nicht die Standardeinstellung 10 benötigen.

Wie @Knossos erwähnt, gibt es eine Option AsyncTaskCompat.executeParallel(task, params);aus der Support v.4-Bibliothek, um Aufgaben parallel auszuführen, ohne sich um die API-Ebene zu kümmern . Diese Methode wurde in API-Level 26.0.0 nicht mehr unterstützt.

UPDATE: 3

Hier ist eine einfache Test-App zum Spielen mit einer Reihe von Aufgaben, serielle oder parallele Ausführung: https://github.com/vitkhudenko/test_asynctask

UPDATE: 4 (danke @penkzhou für den Hinweis)

Ab Android 4.4 AsyncTaskverhält es sich anders als im Abschnitt UPDATE: 2 beschrieben . Es gibt eine Korrektur , um zu verhindern, AsyncTaskdass zu viele Threads erstellt werden.

Vor Android 4.4 (API 19) gab AsyncTaskes folgende Felder:

private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(10);

In Android 4.4 (API 19) werden die obigen Felder folgendermaßen geändert:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

Diese Änderung erhöht die Größe der Warteschlange auf 128 Elemente und reduziert die maximale Anzahl von Threads auf die Anzahl von CPU-Kernen * 2 + 1. Apps können weiterhin dieselbe Anzahl von Aufgaben senden.

Vit Khudenko
quelle
11
Nur zu Ihrer Information, die Größe des Kernpools beträgt 5, aber maximal 128. Dies bedeutet, dass, wenn Sie Ihre Warteschlange füllen, mehr als 5 (bis zu 128) gleichzeitig ausgeführt werden können.
Kabuko
2
@ Kabuko: Du hast absolut Recht. Ich habe dies gerade in Version 2.2 untersucht und bestätigt, dass, wenn bereits 5 Aufgaben ausgeführt werden (Größe des Kernpools 5), der Rest der Aufgaben in die Warteschlange für verzögerte Aufgaben gestellt wird. Wenn die Warteschlange jedoch bereits voll ist (die maximale Warteschlangengröße beträgt 10) ), dann wird ein zusätzlicher Arbeitsthread für die Aufgabe gestartet, sodass die Aufgabe sofort gestartet wird.
Vit Khudenko
1
habe gerade meinen Tag gerettet, Caption Held :) Danke
Karoly
2
Bitte aktualisieren Sie diese Antwort für die neuen kompatiblen Funktionen . Beispiel:AsyncTaskCompat.executeParallel(task, params);
Knossos
1
@Arhimed Bitte aktualisieren Sie die Antwort, da sie AsyncTaskCompat.executeParallel(task, params);jetzt veraltet ist
Kirill Starostin
218

Dies ermöglicht die parallele Ausführung auf allen Android-Versionen mit API 4+ (Android 1.6+):

@TargetApi(Build.VERSION_CODES.HONEYCOMB) // API 11
void startMyTask(AsyncTask asyncTask) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    else
        asyncTask.execute(params);
}

Dies ist eine Zusammenfassung von Arhimeds ausgezeichneter Antwort.

Stellen Sie sicher, dass Sie API Level 11 oder höher als Projekterstellungsziel verwenden. In Eclipse also Project > Properties > Android > Project Build Target. Dadurch wird die Abwärtskompatibilität mit niedrigeren API-Ebenen nicht beeinträchtigt. Keine Sorge, Sie erhalten Lint-Fehler, wenn Sie versehentlich Funktionen verwenden, die später als eingeführt wurden minSdkVersion. Wenn Sie wirklich als Funktionen nutzen eingeführt später möchten minSdkVersion, können Sie diese Fehler mit Anmerkungen zu unterdrücken, aber in diesem Fall müssen Sie kümmern sich um die Kompatibilität nehmen selbst . Genau das ist im obigen Code-Snippet passiert.

Sulai
quelle
3
Musste den TimerTask.THREAD_POOL_EXECUTOR in AsynTask.THREAD_POOL_EXECUTOR ändern, dann funktioniert dieses Beispiel
Ronnie
2
Um Probleme zu void startMyTask(AsyncTask<Void, ?, ?> asyncTask) stornieren, können Sie den Anruf generisch machen: (wenn Ihr doInBackground Void nimmt)
Peter
Sie können den obigen Code noch weiter vereinfachen, wenn Ihre Klasse AsyncTask <Void, Integer, Void> erweitert. Es hat super funktioniert, danke.
Peter Arandorenko
1
Vielen Dank. Ich hatte ein Szenario, in dem ich zwei asynchrone Aufgaben gleichzeitig ausführen muss, um Daten vom Webserver zu lesen, aber ich habe festgestellt, dass der Server nur eine URL empfängt. Solange diese bestimmte Anforderung nicht abgeschlossen ist, trifft die URL der zweiten AsyncTask den Server nicht . Ihre Antwort hat mein Problem gelöst :)
Santhosh Gutta
1
Ich bekomme dafür immer wieder positive Stimmen, danke. :) Aber ich habe den Eindruck, dass viele Leute versuchen, AsyncTask für Netzwerke zu verwenden, bei denen hervorragende Bibliotheken wie Volley , Glide oder Swagger die bessere Wahl sind. Stellen Sie sicher, dass Sie diese überprüfen, bevor Sie AsyncTask verwenden :)
Sulai
21

@ Sulai-Vorschlag allgemeiner gestalten:

@TargetApi(Build.VERSION_CODES.HONEYCOMB) // API 11
public static <T> void executeAsyncTask(AsyncTask<T, ?, ?> asyncTask, T... params) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    else
        asyncTask.execute(params);
}   
AsafK
quelle
allgemeinere Lösung: AsyncTaskCompat.executeParallel (neue MyAsyncTask, myprams);
Vikash Kumar Verma
11

Nur um das neueste Update (UPDATE 4) in @Arhimeds makellose Antwort in die sehr gute Zusammenfassung von @sulai aufzunehmen:

void doTheTask(AsyncTask task) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // Android 4.4 (API 19) and above
        // Parallel AsyncTasks are possible, with the thread-pool size dependent on device
        // hardware
        task.execute(params);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { // Android 3.0 to
        // Android 4.3
        // Parallel AsyncTasks are not possible unless using executeOnExecutor
        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    } else { // Below Android 3.0
        // Parallel AsyncTasks are possible, with fixed thread-pool size
        task.execute(params);
    }
}
Ali Nem
quelle
Oh wow, großartig. Das heißt, es .executeOnExecutor()ist jetzt redundant für API> = KITKAT, ja?
Taslim Oseni
3

Es ist möglich. Meine Android-Geräteversion ist 4.0.4 und android.os.Build.VERSION.SDK_INT ist 15

Ich habe 3 Spinner

Spinner c_fruit=(Spinner) findViewById(R.id.fruits);
Spinner c_vegetable=(Spinner) findViewById(R.id.vegetables);
Spinner c_beverage=(Spinner) findViewById(R.id.beverages);

Und ich habe auch eine Async-Tack-Klasse.

Hier ist mein Spinner-Ladecode

RequestSend reqs_fruit = new RequestSend(this);
reqs_fruit.where="Get_fruit_List";
reqs_fruit.title="Loading fruit";
reqs_fruit.execute();

RequestSend reqs_vegetable = new RequestSend(this);
reqs_vegetable.where="Get_vegetable_List";
reqs_vegetable.title="Loading vegetable";
reqs_vegetable.execute();

RequestSend reqs_beverage = new RequestSend(this);
reqs_beverage.where="Get_beverage_List";
reqs_beverage.title="Loading beverage";
reqs_beverage.execute();

Das funktioniert perfekt. Einer nach dem anderen luden meine Spinner. Ich habe den Benutzer executeOnExecutor nicht verwendet.

Hier ist meine Async-Task-Klasse

public class RequestSend  extends AsyncTask<String, String, String > {

    private ProgressDialog dialog = null;
    public Spinner spin;
    public String where;
    public String title;
    Context con;
    Activity activity;      
    String[] items;

    public RequestSend(Context activityContext) {
        con = activityContext;
        dialog = new ProgressDialog(activityContext);
        this.activity = activityContext;
    }

    @Override
    protected void onPostExecute(String result) {
        try {
            ArrayAdapter<String> adapter = new ArrayAdapter<String> (activity, android.R.layout.simple_spinner_item, items);       
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            spin.setAdapter(adapter);
        } catch (NullPointerException e) {
            Toast.makeText(activity, "Can not load list. Check your connection", Toast.LENGTH_LONG).show();
            e.printStackTrace();
        } catch (Exception e)  {
            Toast.makeText(activity, "Can not load list. Check your connection", Toast.LENGTH_LONG).show();
            e.printStackTrace();
        }
        super.onPostExecute(result);

        if (dialog != null)
            dialog.dismiss();   
    }

    protected void onPreExecute() {
        super.onPreExecute();
        dialog.setTitle(title);
        dialog.setMessage("Wait...");
        dialog.setCancelable(false); 
        dialog.show();
    }

    @Override
    protected String doInBackground(String... Strings) {
        try {
            Send_Request();
            } catch (NullPointerException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        return null;
    }

    public void Send_Request() throws JSONException {

        try {
            String DataSendingTo = "http://www.example.com/AppRequest/" + where;
            //HttpClient
            HttpClient httpClient = new DefaultHttpClient();
            //Post header
            HttpPost httpPost = new HttpPost(DataSendingTo);
            //Adding data
            List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);

            nameValuePairs.add(new BasicNameValuePair("authorized","001"));

            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
            // execute HTTP post request
            HttpResponse response = httpClient.execute(httpPost);

            BufferedReader reader;
            try {
                reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                StringBuilder builder = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    builder.append(line) ;
                }

                JSONTokener tokener = new JSONTokener(builder.toString());
                JSONArray finalResult = new JSONArray(tokener);
                items = new String[finalResult.length()]; 
                // looping through All details and store in public String array
                for(int i = 0; i < finalResult.length(); i++) {
                    JSONObject c = finalResult.getJSONObject(i);
                    items[i]=c.getString("data_name");
                }

            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Sajitha Rathnayake
quelle
2
Wie sehen Sie überhaupt, dass sie nicht nacheinander ausgeführt werden?
siehe
@behelit Ist es wichtig, wann es funktioniert? Bitte lesen Sie Akzeptierte Antwort für weitere Details stackoverflow.com/a/4072832/2345900
Sajitha Rathnayake
2
Entschuldigung, ich verstehe nicht. Wenn Sie nicht feststellen können, ob Ihre Asynchronisierungen sequentiell oder wirklich asynchron sind, spielt dies eine Rolle. In der verknüpften Antwort heißt es außerdem, dass Sie die Methode executeonexecutor verwenden sollen, die Sie in Ihrer Antwort nicht verwenden. Entschuldigung, wenn ich etwas verpasst habe.
siehe
1
"Einer nach dem anderen haben meine Spinner geladen." Sie haben es buchstäblich selbst gesagt, dass dies eine serielle Ausführung ist, keine parallele Ausführung. Jede unserer AsyncTasks wird einer Warteschlange hinzugefügt und nacheinander verarbeitet. Dies beantwortet nicht die Frage des OP.
Joshua Pinter
Dies ist eine sehr alte Antwort. Wenn dies für Sie nicht funktioniert, beziehen Sie sich bitte auf die akzeptierte Antwort
Sajitha Rathnayake
3

Wenn Sie Aufgaben parallel ausführen möchten, müssen Sie die Methode executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "your task name")nach der Android-Version 3.0 aufrufen . Diese Methode ist jedoch vor Android 3.0 und nach 1.6 nicht vorhanden, da sie von selbst parallel ausgeführt wird. Ich empfehle daher, dass Sie Ihre eigene AsyncTask-Klasse in Ihrem Projekt anpassen, um Ausnahmen in anderen Android-Versionen zu vermeiden.

Alpha
quelle