问题
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?
回答1:
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.
回答2:
Introduction
I had gone through this topic for one of my previous project and found different solutions to the problem (I finally used method 1 for the project because it was the best suited one for that specific project). I will share them with you for as simple as possible. For the better understanding, I will explain them using an example of downloading multiple images.
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 methodsvoid 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.
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.
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.
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;
}
}
}
回答3:
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() { ... }
}
来源:https://stackoverflow.com/questions/17418194/waiting-for-multiple-callbacks-in-android