在运行另一个Runnable之前等待一个Runnable完成

22
我有一个Android应用程序,其中包含一个主选项卡活动和几个单独选项卡内的活动。在我的主活动的onCreate()中,我有一个可运行对象,它创建一个列表,在单独的活动中,我利用这个列表。
在单独的活动的onCreate()中,我还有运行在列表上操作的可运行对象。但是,我需要这些可运行对象仅在主选项卡活动的可运行对象完成创建列表时才运行,否则我将得到一个空列表。我正在尝试找到一种优雅的方法来实现这一点。现在,在我的主活动的Runnable中,我设置了一个全局布尔变量isDone,在单独的活动的可运行对象中,我通过while循环等待isDone被设置。这可以工作,但可能不是最好的方法。
有任何想法吗?
谢谢。
编辑:我正在尝试以下代码,但我收到运行时错误:
在MainActivity的Runnable中:
mainRunnable = new Runnable() {
  public void run() {
    try {
      generateList();
      synchronized(this) {
      listDone = true;
      notifyAll();
    }
  } catch (Exception e) {
      Log.e("BACKGROUND_PROC", e.getMessage());
    }
  }
};
Thread thread = new Thread(null, mainRunnable, "Background");
thread.start();

在我的OtherActivity的Runnable中:

otherRunnable = new Runnable() {
  public void run() {
    synchronized(MainActivity.mainRunnable) {
      if (!MainActivity.getListDone()) {
        try {
          wait();
        } catch (InterruptedException e) {
        }
      }
    }
  }
};
Thread thread = new Thread(null, otherRunnable, "Background");
thread.start();

主线程似乎运行完全,但另一个线程似乎导致应用程序崩溃。我收到以下错误消息:

01-10 15:41:25.543: E/WindowManager(7074): Activity com.myapp.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@40539850 that was originally added here
01-10 15:41:25.543: E/WindowManager(7074): android.view.WindowLeaked: Activity com.myapp.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@40539850 that was originally added here
7个回答

20

您可以使用waitnotify方法。

为此,需要一个全局可访问的对象,在程序中此时未被其他任何东西使用该锁。我假设创建列表的Runnable本身可以扮演这个角色。

因此,您可以向创建列表的Runnable类添加以下内容:

private boolean listsDone = false;

boolean getListsDone() {
    return listsDone;
}

在创建完列表后,将以下内容添加到其 run() 方法中:

synchronized (this) {
    listsDone = true;
    notifyAll();
}

在其他需要等待的Runnable方法中,可以像这样执行:

synchronized (listCreatingRunnableObject) {
    if (!listCreatingRunnableObject.getListsDone()) {
        try {
            listCreatingRunnableObject.wait();
        } catch (InterruptedException e) {
            // handle it somehow
        }
    }
}

更新: 为了澄清,两个synchronized块需要同步于相同的对象,并且你必须在该对象上调用wait()notifyAll()。如果对象是Runnable,则可以对于第一个块(如上面的代码)隐式进行,但如果是活动,则需要在两种情况下显式使用活动对象。


谢谢。listCreatingRunnableObject 到底是活动类还是 Runnable? - user1118764
当我尝试在其他Runnable的run()方法中使用你的代码时,我会得到以下错误: 01-10 15:24:27.003: W/dalvikvm(5857): 线程ID = 10:线程退出时出现未捕获异常(组=0x4001d5a0) 01-10 15:24:27.023: E/AndroidRuntime(5857): 致命异常:后台 01-10 15:24:27.023: E/AndroidRuntime(5857): java.lang.IllegalMonitorStateException:在wait()之前未被线程锁定的对象 - user1118764
@user1118764 糟糕!我忘记了在用于同步的对象上调用 wait() 。请查看我答案的更新。 - Taymon
谢谢,那个可行!之前我把要做的事情放在try语句里面,现在我把它们拿出来了,现在它们可以正确地执行。 - user1118764
@user1118764 提醒一下,如果这个答案给了你想要的东西,你可以通过点击复选框来接受它。 - Taymon
显示剩余2条评论

13

您可以像这样使用 Queue

public class RunQueue implemements Runnable
{
  private List<Runnable> list = new ArrayList<Runnable>();

  public void queue(Runnable task)
  {
    list.add(task);
  }

  public void run()
  {
    while(list.size() > 0)
    {
      Runnable task = list.get(0);

      list.remove(0);
      task.run();
    } 
  }
}

使用这种方法可以仅用一个线程而不是多个线程。同时,您可以保留所有现有的“Runnable”对象,同时清理它们为等待和加入而编写的任何代码。


1
我也试图做同样的事情,想到了这个方法,但没想到它会像我想的那样工作。看到这个答案后,我尝试了一下,结果完美地解决了问题。这绝对是实现这个目标最简单的方法。谢谢! - Dave
有没有办法让它在循环中运行?我想让一个线程一遍又一遍地运行(无限),但它们不能堆积。它必须一次只能运行一个。@64BitBob - Riptyde4
@Riptyde4 - 只需将 while/remove 替换为 while(true),并使用 for(int i=0; i<list.size(); i++) {list.get(i).run();}。如果您希望在循环之间等待,请在 while(true) 块的开头或结尾添加 Thread.sleep()。 - 64BitBob
1
应该是 task.run() 吧? - Mukul
@Mukul - 天哪!我简直不敢相信那个漏洞居然被忽略了这么久。我已经更新了帖子来修复这个漏洞。干得好! - 64BitBob
1
“Runnable task = list.get(0);” 似乎会出现不兼容类型的错误。需要先将 list.get() 的结果转换为 Runnable。除此之外,这个代码看起来运行得很好! - user3714134

9

在主线程中设置一个值为1的CountDownLatch,然后让依赖线程等待它。当主线程完成时,您将计数器减少到0,等待者将立即开始。


谢谢,CountDownLatch救了我的一天。 - plamkata__

3

使用while循环的主动等待根本不是一个好主意。最简单的方法是在第一个Runnable的最后一步中启动其余Runnable。如果由于某些原因无法实现这一点,请考虑向Handler发布消息。


谢谢。我尝试了第一种方法,它起作用了,但似乎也不是一个好主意。现在我正在尝试通过等待/通知的方式来实现它。 - user1118764

0

0
也许你可以参考一下 Android 中的 Looper。简单来说,一个线程通过 while 循环从队列中不断获取任务并执行。

0

您使用 Runnable 而不是 Thread 是否有特别的原因?如果您使用 Thread,则可以利用各种线程通信原语,这正是它们存在的原因(尤其是 wait()join())。


除了我使用的示例代码声明了一个Runnable myRunnable并通过以下调用运行它之外,没有其他原因: Thread thread = new Thread(null, myRunnable, "background"); thread.start 不确定这是一个Runnable还是一个线程? - user1118764
@user1118764 你可能仍然可以直接访问线程对象。但是,如果你使用wait()(正如我在我的回答中建议的那样),你就不需要这样做了。只有当你想要等待特定线程完全执行完毕而不仅仅是完成某个特定任务时,才需要使用join() - Taymon
如果你使用new Thread(),那么你正在处理一个线程。所以你可以继续使用我提到的通信机制(并由Taymon在这个回答中详细描述 - https://dev59.com/YGoy5IYBdhLWcg3wD57G#8799584)。 - curioustechizen

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