Wait for multiple AsyncTask to complete

12,611

Solution 1

You could also simply decrement a counter in a shared object as part of onPostExecute. As onPostExecute runs on the same thread (the main thread), you won't have to worry about synchronization.

UPDATE 1

The shared object could look something like this:

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

UPDATE 2

According to the comments for this answer, OP is looking for a solution in which he can avoid building a new class. This can be done by sharing an AtomicInteger among the 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"));    
        }
    }
}

Solution 2

You should use a CountDownLatch. Here the documentation with examples: java.util.concurrent.CountDownLatch

Basically you give a reference of CountDownLatch to your threads, and each of them will decrement it when finished:

countDownLatch.countDown();

The main thread will wait on the termination of all threads using:

countDownLatch.await();

Solution 3

First, add this class to your project

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

Then:

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

Then in each task - when completed, add the line: multiTaskHandler.taskComplete();

Example:

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

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

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

You can use multiTaskHandler.cancel() if you want to cancel the code that runs when all the tasks have completed. For instance - if you have an error (don't forget to also cancel all the other tasks).

* This solution will not pause the main thread!

Share:
12,611
Nicholas Allio
Author by

Nicholas Allio

iOS Developer, Computer engineer, technology lover, and mobile development fanatic.

Updated on June 11, 2022

Comments

  • Nicholas Allio
    Nicholas Allio almost 2 years

    I am parallelizing my operation by splitting it in the exact number of cores available and then, by start the same number of AsyncTask, performing the same operation but on different portions of data.

    I am using executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ...) in order to parallelize the execution of them.

    I would like to know when every thread finishes its job so that combine all results and perform further operations.

    How can I do?

  • Nicholas Allio
    Nicholas Allio over 8 years
    I think in this way you may have problem of multiple access and modification of the same variable by multiple threads if they finish at the same time..
  • Janus Varmarken
    Janus Varmarken over 8 years
    No, as onPostExecute is run on the main thread. Just don't access the variable as part of doInBackground.
  • Nicholas Allio
    Nicholas Allio over 8 years
    What is its behavior in case of concurrent access to the CountDownLatch?
  • Nicholas Allio
    Nicholas Allio over 8 years
    So you will propose to let the activity wait in an idle loop till the counter reaches zero? Not sure is the best solution then..
  • Janus Varmarken
    Janus Varmarken over 8 years
    No, that would definitely not be a good solution (in fact the activity would probably be closed by the system due to unresponsiveness). You could instead, as part of setting the counter in your shared object, check if the updated value has reached 0. If that is the case, do a local broadcast and listen for that inside your activity.
  • Pouriya Zarbafian
    Pouriya Zarbafian over 8 years
    It is thread safe as a CountDownLatch is a synchronization tool.
  • Nicholas Allio
    Nicholas Allio over 8 years
    For sure this will work but I was looking for something more handy
  • Janus Varmarken
    Janus Varmarken over 8 years
    What do you mean by more handy? Builtin functionality? Added example of the proposed class. It's pretty simply IMO.
  • Nicholas Allio
    Nicholas Allio over 8 years
    Handy in a sense that I would avoid to build a new class exclusively for this purpose
  • Janus Varmarken
    Janus Varmarken over 8 years
    I see. I have an alternative approach that you may like more (personally I would prefer the one posted). I will update the answer with the different strategy, give me a few minutes.
  • Nicholas Allio
    Nicholas Allio over 8 years
    This solution is very interesting to me but the problem is that you can use it only if you do not have parameters to pass to your AsyncTask (in my case I pass to my task several Integers and I can not pass a CountDownLatch at the same time and refer to it in onPostExecute is not working properly
  • Pouriya Zarbafian
    Pouriya Zarbafian over 8 years
    Maybe you could extend the AsyncTask and create a constructor with an extra parameter, and override the onPostExecute by calling 'super.onPostExecute()' and then 'countDownLatch.countDown()'.
  • Janus Varmarken
    Janus Varmarken over 8 years
    Added alternative solution that uses an AtomicInteger inside your existing AsyncTask implementation to deduce if all jobs have finished.
  • Janus Varmarken
    Janus Varmarken over 5 years
    "The main thread will wait on the termination of all threads using: countDownLatch.await()" you'd actually need to dedicate a separate thread other than the main thread for this await call as otherwise you'd be locking up the GUI (and thereby defeating the purpose of AsyncTask, i.e., to perform short-lived background jobs off of the main thread). Upon progressing beyond the await call, this thread dedicated for waiting would then have to signal back to the main thread that the jobs were completed by sending a broadcast or posting to a Handler associated with the main thread.
  • Duna
    Duna over 4 years
    The upper solutions are bad and slow, use merge operator from Rx
  • Janus Varmarken
    Janus Varmarken over 4 years
    @Duna you're arguing that it's slow to decrement an int variable in a shared object? This is not a discussion of whether or not to use AsyncTask as the OP's question was in the context of AsyncTasks.
  • MurifoX
    MurifoX over 4 years
    This solution is just perfect.
  • Bogdan Android
    Bogdan Android over 3 years
    Match very good with my personal problem, adapted it to my needs. Thumb up!