6

In Java threads, you could have some number of threads in a list, start them off, and have a main thread join one, then another, going through and waiting for all processes to complete before moving on.

In other models, I'm not sure how you would do that. Take the RootTools 3.0 Command class for example. You create a Command which has three methods, commandOutput, commandFinished, commandTerminated, and while you can use a callback to do something at the end of a process, I don't know how you would wait for multiple processes (For example, going through listing of several directories and summing the file-sizes).

I believe the Android Asynctask would have a similar problem - you can easily make a callback, but there is no way to wait for several tasks. Unless I'm missing something?

2
  • This SO question might be a good start. <br><br> The idea is simple, give a bunch of tasks to the executor which takes care of running them in parallel (even having some return objects if req) and wait for the tasks to complete.
    – psykid
    Commented Jan 20, 2018 at 19:57
  • AsyncTask is deprecated - use Kotlin Coroutines which have ample methods for waiting for things to complete. Commented Dec 1, 2021 at 5:09

3 Answers 3

5

##Introduction

I had gone through this topic for one of my previous project and found different solutions to the problem .I finally used the first method for the project because it was the best suited one for that specific project.

Let ImageDownloader be a class to download an image from a URL asynchronously and it has the following properties.

  • An interface - ImageDownloadCallback to get the callback when the task is completed. It has two methods
  • void onSuccess(String imagePath) : called when the task is completed successfully.
  • void onFailure() : called when the task is failed to complete.
  • A method - download(String url, ImageDownloadCallback callback) to start the download task

PauseModeCallbackHandler, ChainModeCallbackHandler and ParallelModeCallbackHandler are wrapper classes of the callbacks for the three methods respectively. You can customize them according to what task you want to do.




##Method 1: Execute the tasks one by one by pausing the starter thread.

Illustration of solution 1

Pros
Gets the results in the original thread

Cons
Need to make the thread waiting


###ThreadLockedTask You can use this class to make the thread waiting until the result is obtained.
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author Ahamad Anees P.A
 * @version 1.0
 * @param <T> type
 */
public class ThreadLockedTask<T> {

    private AtomicReference<ResultWrapper<T>> mReference;

    public ThreadLockedTask() {
        mReference = new AtomicReference<>(new ResultWrapper<T>());
    }

    public T execute(Runnable runnable) {
        runnable.run();
        if (!mReference.get().mIsSet)
            lockUntilSet();
        return mReference.get().mResult;
    }

    private void lockUntilSet() {
        synchronized (this) {
            while (!mReference.get().isSet()) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public void setResult(T result) {
        synchronized (this) {
            ResultWrapper<T> wrapper = mReference.get();
            wrapper.setResult(result);
            wrapper.setIsSet(true);
            notify();
        }
    }

    public static class ResultWrapper<T> {
        private boolean mIsSet;
        private T mResult;

        public boolean isSet() {
            return mIsSet;
        }

        public T getResult() {
            return mResult;
        }

        void setIsSet(boolean isCompleted) {
            this.mIsSet = isCompleted;
        }

        void setResult(T result) {
            this.mResult = result;
        }
    }

}

###Sample

import java.util.ArrayList;
import java.util.List;

public class PauseModeCallbackHandler {

    // List of results
    private static List<String> results;

    public static void start(final List<String> urls, final ImageDownloader.ProgressUpdateListener listener) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                results = new ArrayList<>();

                // Do tasks one by one
                for (final String url :
                        urls) {

                    //Here the result is a String. Change "String" in the following two lines for other datatypes.
                    final ThreadLockedTask<String> task = new ThreadLockedTask<>();
                    final String imagePath = task.execute(new Runnable() {
                        @Override
                        public void run() {
                            //Start the task here
                            ImageDownloader.getInstance(listener).download(url,
                                    new ImageDownloader.ImageDownloadCallback() {
                                        @Override
                                        public void onSuccess(String imagePath) {
                                            //Set the result on success
                                            task.setResult(imagePath);
                                        }

                                        @Override
                                        public void onFailure() {
                                            //Set result as null on failure
                                            task.setResult(null);
                                        }
                                    });
                        }
                    });

                    if (imagePath!=null)
                        results.add(imagePath);

                }

                afterCallbacks();
            }
        }).start();
    }

    private PauseModeCallbackHandler() {}

    private static void afterCallbacks() {
        // All tasks completed. List "results" now holds the result

        DemoActivity.isTasksInProgress = false;
    }
}




##Method 2: Execute tasks from the callback of the previous one like a chain reaction.

Illustration of solution 2


Sample

import java.util.ArrayList;
import java.util.List;

public class ChainModeCallbackHandler implements ImageDownloader.ImageDownloadCallback {

    // List of args to start the task. Use pojo classes if your task has multiple args
    private static List<String> urls;

    // List of results
    private static List<String> results;

    // Optional.
    private static ImageDownloader.ProgressUpdateListener progressUpdateListener;

    // Leave it as it is
    private int index;

    public static void start(List<String> urls, ImageDownloader.ProgressUpdateListener listener) {
        ChainModeCallbackHandler.urls = urls;
        results = new ArrayList<>();
        progressUpdateListener = listener;

        //Start with the first task
        ImageDownloader.getInstance(listener).download(urls.get(0), new ChainModeCallbackHandler(0));
    }

    private ChainModeCallbackHandler(int index) {
        this.index = index;
    }

    @Override
    public void onSuccess(String imagePath) {
        results.add(imagePath);
        afterCallback();
    }

    @Override
    public void onFailure() {
        afterCallback();
    }

    private void afterCallback() {
        int nextIndex = index+1;
        if (nextIndex<urls.size()) {
            //Tasks are not completed yet. Do next task
            ImageDownloader.getInstance(progressUpdateListener).download(urls.get(nextIndex),
                    new ChainModeCallbackHandler(nextIndex));
        } else {
            // All tasks completed. List "results" now holds the result

            DemoActivity.isTasksInProgress = false;
        }
    }
}




##Method 3: Execute tasks in parallel.

Illustration of solution 3

Pros
Parallel execution helps to save time sometimes


Sample

import java.util.ArrayList;
import java.util.List;

public class ParallelModeCallbackHandler {

    // List of args to start the task. Use pojo classes if your task has multiple args
    private static List<String> urls;

    // List of results
    private static List<String> results;

    // Leave it as it is
    private static int count;

    public static void start(List<String> urls, ImageDownloader.ProgressUpdateListener listener) {
        ParallelModeCallbackHandler.urls = urls;
        results = new ArrayList<>();
        count = 0;

        // Start all tasks
        for (String url :
                urls) {
            //Replace with your task and its callback
            ImageDownloader.getInstance(listener).download(url, new ImageDownloader.ImageDownloadCallback() {
                @Override
                public void onSuccess(String imagePath) {
                    results.add(imagePath);
                    afterCallback();
                }

                @Override
                public void onFailure() {
                    afterCallback();
                }
            });
        }
    }

    private ParallelModeCallbackHandler() {}

    private static void afterCallback() {
        if (++count==urls.size()) {
            // All tasks completed. List "results" now holds the result

            DemoActivity.isTasksInProgress = false;
        }
    }
}
2

You can call wait() on the command that you are executing.

Although before doing this you should turn off the handler for the command that you call wait on. You can do that for every command by setting RootTools.handlerEnabled to false or by using the constructor in each individual command and passing in false to disable the handler for that command.

This is important because if the handler is used then it will try to call the callback methods on the thread that you called wait() and that will result in a deadlock.

When you turn the handler off for the command and call wait() the command will call notifyAll() when it completed so your thread will resume.

The negative side of this is that the callback methods will no longer be done in the thread that you are working on so you will not be able to do any UI work from those callback methods unless you implement a handler or some other acceptable solution to deal with this.

1
  • 1
    Huh, I found this thread searching for "method callback waiting". And guess what. I've had exactly the same problem with RootTools - a deadlock. handlerEnabled = false solved the problem. If it weren't for you answering here we would have stuck :) Commented Jan 12, 2014 at 13:45
0

Use CountDownLatch, I'll copy here their usage example for better syntax highlighting (:

class Driver { // ...
  void main() throws InterruptedException {
    CountDownLatch startSignal = new CountDownLatch(1);
    CountDownLatch doneSignal = new CountDownLatch(N);

    for (int i = 0; i < N; ++i) // create and start threads
      new Thread(new Worker(startSignal, doneSignal)).start();

    doSomethingElse();            // don't let run yet
    startSignal.countDown();      // let all threads proceed
    doSomethingElse();
    doneSignal.await();           // wait for all to finish
  }
}

class Worker implements Runnable {
  private final CountDownLatch startSignal;
  private final CountDownLatch doneSignal;
  Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
    this.startSignal = startSignal;
    this.doneSignal = doneSignal;
  }
  public void run() {
    try {
      startSignal.await();
      doWork();
      doneSignal.countDown();
    } catch (InterruptedException ex) {} // return;
  }

  void doWork() { ... }
}
2
  • Isn't this the same as joining threads? I need to do this on callbacks.
    – NoBugs
    Commented Jul 2, 2013 at 14:41
  • Well... It is different in some ways, one could use doneSignal.await() in the Worker's run() method after the doneSignal.countDown() for a more callback-like waiting approach, but I'm not sure that I understood your original problem. Can you rephrase it please?
    – Miguel
    Commented Jul 4, 2013 at 9:34

Not the answer you're looking for? Browse other questions tagged or ask your own question.