web-dev-qa-db-fra.com

Attendez que plusieurs tâches asynchrones soient terminées

Je parallélise mon opération en la divisant dans le nombre exact de cœurs disponibles, puis en démarrant le même nombre de tâches Async, en effectuant la même opération mais sur différentes parties de données.

J'utilise executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ...) afin de paralléliser leur exécution.

Je voudrais savoir quand chaque thread termine son travail afin que tous les résultats soient combinés et effectuer d'autres opérations.

Comment puis-je faire?

13
Nicholas Allio

Vous pouvez également simplement décrémenter un compteur dans un objet partagé dans le cadre de onPostExecute. Comme onPostExecute s'exécute sur le même thread (le thread principal), vous n'avez pas à vous soucier de la synchronisation.

MISE À JOUR 1

L'objet partagé pourrait ressembler à quelque chose comme ceci:

public class WorkCounter {
    private int runningTasks;
    private final Context ctx;

    public WorkCounter(int numberOfTasks, Context ctx) {
        this.runningTasks = numberOfTasks;
        this.ctx = ctx;
    }
    // Only call this in onPostExecute! (or add synchronized to method declaration)
    public void taskFinished() {
        if (--runningTasks == 0) {
            LocalBroadcastManager mgr = LocalBroadcastManager.getInstance(this.ctx);
            mgr.sendBroadcast(new Intent("all_tasks_have_finished"));
        }
    }
}

MISE À JOUR 2

Selon les commentaires relatifs à cette réponse, OP recherche une solution lui permettant d'éviter de créer une nouvelle classe. Cela peut être fait en partageant un AtomicInteger parmi les spawned AsyncTasks:

// TODO Update type params according to your needs.
public class MyAsyncTask extends AsyncTask<Void,Void,Void> {
    // This instance should be created before creating your async tasks.
    // Its start count should be equal to the number of async tasks that you will spawn.
    // It is important that the same AtomicInteger is supplied to all the spawned async tasks such that they share the same work counter.
    private final AtomicInteger workCounter;

    public MyAsyncTask(AtomicInteger workCounter) {
        this.workCounter = workCounter;
    }

    // TODO implement doInBackground

    @Override
    public void onPostExecute(Void result) {
        // Job is done, decrement the work counter.
        int tasksLeft = this.workCounter.decrementAndGet();
        // If the count has reached zero, all async tasks have finished.
        if (tasksLeft == 0) {
            // Make activity aware by sending a broadcast.
            LocalBroadcastManager mgr = LocalBroadcastManager.getInstance(this.ctx);
            mgr.sendBroadcast(new Intent("all_tasks_have_finished"));    
        }
    }
}
12
Janus Varmarken

Vous devriez utiliser un CountDownLatch. Voici la documentation avec des exemples: Java.util.concurrent.CountDownLatch

Fondamentalement, vous donnez une référence de CountDownLatch à vos threads, et chacun d'eux le décrémentera une fois terminé:

countDownLatch.countDown();

Le thread principal attendra la fin de tous les threads en utilisant:

countDownLatch.await();
5
Pouriya Zarbafian

Une autre option pourrait être de stocker tous vos nouveaux threads dans un tableau.

Ensuite, vous pouvez parcourir le tableau et attendre avec le fil [i] .join que le fil se termine.

voir join () http://developer.Android.com/reference/Java/lang/Thread.html#Thread(Java.lang.Runnable)

Lorsque l'itération est terminée, tous vos threads sont terminés et vous pouvez travailler

0
Andriel

Tout d'abord, ajoutez cette classe à votre projet

public abstract class MultiTaskHandler {
    private int mTasksLeft;
    private boolean mIsCanceled = false;

    public MultiTaskHandler(int numOfTasks) {
        mTasksLeft = numOfTasks;
    }

    protected abstract void onAllTasksCompleted();

    public void taskComplete()  {
        mTasksLeft--;
        if (mTasksLeft==0 && !mIsCanceled) {
            onAllTasksCompleted();
        }
    }

    public void reset(int numOfTasks) {
        mTasksLeft = numOfTasks;
        mIsCanceled=false;
    }

    public void cancel() {
        mIsCanceled = true;
    }
}

Ensuite: 

int totalNumOfTasks = 2; //change this to the number of tasks that you are running
final MultiTaskHandler multiTaskHandler = new MultiTaskHandler(totalNumOfTasks) {
    @Override
    protected void onAllTasksCompleted() {
       //put the code that runs when all the tasks are complete here
    }
};

Ensuite, dans chaque tâche - lorsque vous avez terminé, ajoutez la ligne: multiTaskHandler.taskComplete();

Exemple:

(new AsyncTask<Void,Void,Void>() {

    @Override
    protected Void doInBackground(Void... voids) {
        // do something...
        return null;
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        multiTaskHandler.taskComplete();
    }
}).execute();

Vous pouvez utiliser multiTaskHandler.cancel() si vous souhaitez annuler le code exécuté lorsque toutes les tâches sont terminées. Par exemple, si vous rencontrez une erreur (n'oubliez pas d'annuler également toutes les autres tâches).

* Cette solution ne mettra pas en pause le thread principal!

0
Gil SH