在Android中等待多个回调

6
在Java线程中,您可以在列表中拥有一些线程,启动它们,并让主线程join一个线程,然后是另一个线程,以此类推,等待所有进程完成后再继续进行。
在其他模型中,我不确定该怎么做。以 RootTools 3.0 的Command类为例。您创建了一个具有三个方法 commandOutput commandFinished commandTerminated Command,虽然您可以使用回调来在进程结束时执行某些操作,但我不知道如何等待多个进程(例如,遍历几个目录并对文件大小进行求和)。
我认为Android Asynctask会有类似的问题-您可以轻松地进行回调,但没有办法等待多个任务。除非我漏掉了什么?

这个 Stack Overflow 的问题可能是一个不错的起点。思路很简单,将一堆任务交给执行器来并行运行它们(如果需要,甚至可以返回一些对象),然后等待任务完成。 - psykid
AsyncTask已被弃用 - 使用Kotlin协程,它具有丰富的等待完成事物的方法。 - dominicoder
3个回答

5

##简介

我曾在之前的一个项目中研究过这个主题,并发现了不同的解决方案。最终,我选择了第一种方法,因为它最适合那个具体的项目。

假设有一个名为ImageDownloader的类来异步下载来自URL的图像,它具有以下属性。

  • 一个接口 - ImageDownloadCallback用于在任务完成时获取回调。它有两种方法
  • void onSuccess(String imagePath):任务成功完成时调用。
  • void onFailure():任务未能完成时调用。
  • 一个方法 - download(String url, ImageDownloadCallback callback)用于启动下载任务

PauseModeCallbackHandlerChainModeCallbackHandlerParallelModeCallbackHandler分别是三种方法的回调包装类。您可以根据要执行的任务进行自定义。




##方法1: 通过暂停启动线程一个接一个地执行任务。

Illustration of solution 1

优点
在原始线程中获取结果

缺点
需要使线程等待


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

}

###示例

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




##方法 2: 像连锁反应一样从上一个回调函数执行下一个任务。

解决方案 2 的插图


示例

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




##方法三: 并行执行任务。

解决方案3的插图

优点
并行执行有时可以节省时间。


示例

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

你可以在正在执行的命令上调用wait()方法。

但在此之前,你应该关闭调用wait()方法的命令的处理程序。你可以通过将RootTools.handlerEnabled设置为false或者在每个单独的命令中使用构造函数并传递false来禁用该命令的处理程序来实现这一点。

这很重要,因为如果使用了处理程序,它将尝试在你调用wait()方法的线程上调用回调方法,这将导致死锁。

当你关闭命令的处理程序并调用wait()方法时,命令将在完成后调用notifyAll()方法,所以你的线程将恢复。

这样做的负面影响是,回调方法将不再在你正在工作的线程上执行,因此,除非你实现一个处理程序或其他可接受的解决方案来处理此问题,否则你将无法从这些回调方法中进行任何UI工作。


1
哎呀,我是在搜索“方法回调等待”时找到了这个线程。你猜怎么着?我也遇到了RootTools的死锁问题。将handlerEnabled = false解决了这个问题。如果不是你在这里回答,我们就会卡住 :) - Yaroslav Mytkalyk

0

使用 CountDownLatch,我会在这里复制他们的使用示例以获得更好的语法高亮(:

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() { ... }
}

这不就是线程加入吗?我需要在回调函数中执行此操作。 - NoBugs
嗯...在某些方面是不同的,一个更像回调函数的等待方法是在工作线程的run()方法中使用doneSignal.await(),在doneSignal.countDown()之后。但我不确定我是否理解了你的原始问题。你能换一种说法吗? - Miguel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接