【问题标题】:How to use Runnable.wait() in AsyncTask? Why does the AsyncTask NOT wait...?如何在 AsyncTask 中使用 Runnable.wait()?为什么 AsyncTask 不等待...?
【发布时间】:2014-07-03 08:26:20
【问题描述】:

我正在使用 AsyncTask 运行后台操作。当然,在已经在后台线程中工作时切换到另一个线程通常没有多大意义,除了另一个线程是 UI 线程。这就是我想要的:当任务运行时,我需要“访问”用户界面,例如显示一个对话框,询问用户如何继续。

  • 运行后台任务
  • 停止任务以获取用户反馈
  • 切换到 UI 线程以显示对话框并要求输入
  • 切换回后台任务并继续工作

如何做到这一点?我以为我可以将RunnablemyActivity.runOnUiThread(runnable) 一起使用,但这不起作用:

private void copyFiles() {
    CopyTask copyTask = new CopyTask(this);
    copyTask.execute();
}

// CustomAsyncTask is a AsyncTask subclass that takes a reference to the current
// activity as parameter
private class CopyTask extends CustomAsyncTask<Void, Void, Void> {
    private doCopy;

    @Override
    protected Boolean doInBackground(Void... params) {              
        // Custom code, e.g. copy files from A to B and check for conflict
        for (File file : allFiles) {
            doCopy = true;

            if (isConflict(file)) {
                // Stop current thread and ask for user feedback on UI Thread

                Runnable uiRunnable = new Runnable() {
                    public void run() {
                        // Pos 1. --> Execute custom code, e.g. use AlertDialog to ask user if file should be replaced...
                        doCopy = false;

                        synchronized (this) {
                           this.notify();
                        }
                    }
                });  

                synchronized(uiRunnable) {
                    // Execute code on UI thread
                    activity.runOnUiThread(uiRunnable);

                    // Wait until runnable finished
                    try {
                        uiRunnable.wait();
                    }
                    catch (InterruptedException e ) {
                        e.printStackTrace();
                    }
                }
            }

            // Pos 2. --> Continue work
            if (doCopy)
               copyFromAToB(File);
        }

        return null;
    }
}

doInBackground() 内(--> 在后台线程中)AsyncTask 调用activity.runOnUiThread(uiRunnable)。接下来调用uiRunnable.wait()。关于docu wait() 应该做到以下几点:

使调用线程等待直到另一个线程调用 此对象的 notify() 或 notifyAll() 方法。

因此,后台线程应该等到this.notify() (== uiRunnable.notifiy()) 在另一个线程(= UI 线程)上被调用后才能继续工作,不是吗?

好吧,id 不等待! 调用uiRunnable.wait() 后,后台线程立即继续跳转到if (doCopy)...。似乎后台线程和主线程是并行执行的(这并不奇怪,因为这是线程所做的......),因此无论是 UI 线程上的 doCopy = false 还是后台线程上的 if (doCopy) 都是竞争条件最先到达。

这怎么可能?为什么wait() 没有按描述工作? 还是我有什么问题?

非常感谢!

编辑: 为了避免误解:我当然知道 AsyncTask 的生命周期方法,但据我了解,它们不是我想要的(请参阅我对评论的回复)。

一旦需要 UI 交互就中断 AsyncTask,当然可以查询 UI 并启动一个新的 AsyncTask。然而,这会导致代码非常难以阅读/理解/维护。

据我了解wait() 的文档,这里一切正常。主要问题不是如何在 AsyncTask 的生命周期中进行 UI 交互,而是为什么 wait() 不能按预期工作。

【问题讨论】:

  • 请阅读异步任务的生命周期方法!
  • 当然,我知道这些方法。但它们只允许在任务运行之前之后进行 UI 更改。 onProgressUpdate()可以在执行期间使用,但关于文档,不保证该方法何时运行。后台线程将继续其工作,而不是等待onProgressUpdate() 返回。但这就是我要找的。因此,在这种特殊情况下,这些方法没有帮助,不是吗?
  • 然后基本上你需要将你的任务分成几部分——处理然后更新 UI——处理然后更新 UI 否则你可能想要考虑一个 Handler 线程。
  • 谢谢。请看看我的编辑。将进程拆分为多个 AsyncTask 可以解决问题,但我认为这不是一个很好的解决方案。此外,主要问题是为什么wait()没有按预期工作,而不是如何规避它没有的事实:)
  • 顺便声明activity在哪里?我只是一起完成了您的代码的几乎完全相同的副本,它工作正常。也许它没有等待,因为isConflict(file) 是真的,所以等待/通知可运行部分无论如何都不会运行。

标签: java android multithreading android-asynctask runnable


【解决方案1】:

基础知识

当您首先启动 AsyncTask 时,onPreExecute() 方法会在 UI 线程上运行。您可以覆盖此方法以在 doInBackground() 方法运行之前对 UI 进行更改。

doInBackground() 方法完成后,onPostExecute() 方法在 UI 线程上运行,因此您可以使用它从此处对 UI 进行更改。如果您需要在doInBackground() 方法期间 对 UI 线程进行定期更改,请覆盖在 UI 线程上运行的 onProgressUpdate() 方法,然后从 doInBackground() 中调用它,这将允许您定期更新 UI。

你可以使用类似下面的东西;

private class DoStuffTask extends AsyncTask {

   @Override
   protected void doInBackground(Object... args) {
       // Do stuff
       onProgressUpdate(x);
       // Do more stuff
   }

   @Override
   protected void onProgressUpdate(Object... args) {
       // Update your UI here
   }
}

现在,如果这还不能完全做到,并且您希望 AsyncTask 在doInBackground() 方法期间等待输入,那么可能值得考虑使用多个AsyncTasks。然后您可以完成每个AsyncTask,请求输入,然后开始一个新的AsyncTask 以继续工作。

鉴于AlertDialog 实例是异步的,这可能是首选解决方案,因为您可以从AlertDialog 本身启动下一个AsyncTask

在 AsyncTask 中使用 wait()

如果您希望使用单个 AsyncTask,您可以在 AsyncTask 中使用 wait 来阻止执行继续,直到满足某些条件。在这个实例中,我们只使用两个线程,而不是使用新的Runnable,运行doInBackground() 的线程和主线程,我们在AsycTask 本身上进行同步。

以下示例;

public class TestTask extends AsyncTask{

    private boolean notified;
    private Promptable p;
    public interface Promptable { public abstract void prompt(); }

    public TestTask(Promptable p){
        this.p = p;
    }

    @Override
    protected Object doInBackground(Object... arg0) {
        Log.d("First", "First");
        onProgressUpdate(null);
        synchronized(this){
            while(!notified){
                try{
                    this.wait();
                }
                catch(InterruptedException e){ }                    
            }
        }
        Log.d("Second", "Second");
        return null;
    }

    @Override 
    protected void onProgressUpdate(Object... args){       
        synchronized(this){
            notified = true;
            p.prompt();               
            this.notify();
        }
    }
}

在上面的示例中,假设您的Activity 被解析为AsyncTask 的构造函数,并且它实现了我们创建的名为Promptable 的接口。您会注意到,即使我们调用wait(),我们也将其置于一个while 循环中。如果我们不这样做,并且以某种方式在wait() 之前调用了notify(),那么您的线程将无限期锁定。此外,您不能依赖线程将永远等待这一事实,因此 while 循环可确保在调用 notify 之前它不会继续。

我希望这会有所帮助。

【讨论】:

  • 当然,我知道这些方法。但它们只允许在任务运行之前之后进行 UI 更改。 onProgressUpdate()可以在执行期间使用,但对于文档,不能保证此方法何时运行。后台线程将继续其工作,而不是等待onProgressUpdate() 返回。但这就是我要找的。因此,在这种特殊情况下,这些方法没有帮助,不是吗?
  • 好点。在这种情况下,我会使用多个 AsyncTasks 来完成您的工作。当您的第一个任务完成后,产生提示,然后开始下一个以继续工作。
  • 谢谢。请看看我的编辑。将进程拆分为多个 AsyncTask 可以解决问题,但我认为这不是一个很好的解决方案。此外,主要问题是为什么wait() 不能按预期工作,而不是如何规避它不能工作的事实:)
  • @AndreiHerford - 酷。我会重新阅读,看看我能不能想出什么,但是手动处理线程以在 Android 中使用 UI 是出了名的痛苦。您可以使用 while 循环暂停 doInBackground() 线程,该循环在继续之前等待 onProgressUpdate() 要求的响应。
  • 嗨,我正要写。我正在使用while(notDoneYet),它运行良好。非常感谢!
猜你喜欢
  • 1970-01-01
  • 2020-09-18
  • 1970-01-01
  • 1970-01-01
  • 2016-04-22
  • 1970-01-01
  • 2017-06-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多