从辅助线程运行代码到主线程?

34

首先,这是一个普遍的Java问题,而不是Android问题!

我想知道如何从辅助线程的上下文中在主线程上运行代码。例如:

new Thread(new Runnable() {
        public void run() {
            //work out pi to 1,000 DP (takes a while!)

            //print the result on the main thread
        }
    }).start();

那种情况——我意识到我的例子有点差,因为在Java中你不需要在主线程中打印出东西,并且Swing也有一个事件队列。但是一般情况下,当你需要在后台线程的上下文中运行一个可运行对象(Runnable)时,可能需要在主线程上运行。

补充说明:为了比较,在Objective-C中我会这样做:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0UL), ^{
    //do background thread stuff

    dispatch_async(dispatch_get_main_queue(), ^{
        //update UI
    });
});

提前感谢!

6个回答

33

没有普遍的方法只是将一些代码发送到另一个正在运行的线程并说“嘿,你,干这个。”您需要将主线程置于一种状态,以便它具有接收工作的机制,并正在等待要执行的工作。

以下是设置主线程等待从其他线程接收工作并在到达时运行它的简单示例。显然,您希望添加一种实际结束程序等方式...

public static final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();

public static void main(String[] args) throws Exception {
    new Thread(new Runnable(){
        @Override
        public void run() {
            final int result;
            result = 2+3;
            queue.add(new Runnable(){
                @Override
                public void run() {
                    System.out.println(result);
                }
            });
        }
    }).start();

    while(true) {
        queue.take().run();
    }
}

您可以让主线程执行其他工作,并定期检查队列,而不是无所事事地阻塞它。但在某些时候,这样做就不值得自己动手编写了。Swing提供了完美的工具 :) - Affe
1
实际上,对于我正在做的事情来说,这是一个相当不错的想法 - 我没有使用Swing,因为项目实际上是使用Slick2d引擎编写的简单游戏(否则你是正确的; 我会使用Swing的内置工具!) - Javawag
2
你认为“漂亮”的iOS API在底层的实现是什么样子呢? :) Java/Android也有漂亮的API,但原始问题特别询问了如何在不使用库工具的情况下实际运作。 - Affe
1
@János,在Android中我们有looper和handler,你可以使用它们将一个runnable对象发布到UI线程。这是纯Java的,looper和handler在幕后做了什么。 - Amir Ziarati
@Javawag 即使您的项目中没有使用 Swing,您仍然可以使用 Swing 的线程工具。 - Tech Expert Wizard
显示剩余2条评论

29

如果您正在使用Android,则可以使用Handler来完成此工作?

new Handler(Looper.getMainLooper()).post(new Runnable () {
    @Override
    public void run () {
        ...
    }
});

1
我知道有一个叫做 runOnUIThread() 的方法,但并不是每种情况下的代码都是在 Activity 中编写的。这个答案节省了我很多时间。谢谢! - ManuQiao
4
这个问题并不涉及安卓,甚至没有安卓标签。 - Amir Ziarati
1
是的,但他扩展了之前的答案。这使得像我这样有相同问题的人可以获得关于运行Java的不同平台的清晰描述。 - Patrick Sturm
这是关于Java的问题。 - Manoj Behera

7

虽然这是一次旧的讨论,但如果涉及到向主线程发送请求(而不是相反的方向),你也可以使用 futures 来完成。基本目的是在后台执行某些操作,并在完成后获取结果:

public static void main(String[] args) throws InterruptedException, ExecutionException {
    // create the task to execute
    System.out.println("Main: Run thread");
    FutureTask<Integer> task = new FutureTask<Integer>(
            new Callable<Integer>() {

                @Override
                public Integer call() throws Exception {
                    // indicate the beginning of the thread
                    System.out.println("Thread: Start");

                    // decide a timeout between 1 and 5s
                    int timeout = 1000 + new Random().nextInt(4000);

                    // wait the timeout
                    Thread.sleep(timeout);

                    // indicate the end of the thread
                    System.out.println("Thread: Stop after " + timeout + "ms");

                    // return the result of the background execution
                    return timeout;
                }
            });
    new Thread(task).start();
    // here the thread is running in background

    // during this time we do something else
    System.out.println("Main: Start to work on other things...");
    Thread.sleep(2000);
    System.out.println("Main: I have done plenty of stuff, but now I need the result of my function!");

    // wait for the thread to finish if necessary and retrieve the result.
    Integer result = task.get();

    // now we can go ahead and use the result
    System.out.println("Main: Thread has returned " + result);
    // you can also check task.isDone() before to call task.get() to know
    // if it is finished and do somethings else if it is not the case.
}

如果您的意图是在后台执行多个任务并获取结果,则可以按上述方法设置一些队列,或者将进程拆分为多个 future(同时启动所有进程或在需要时启动新进程,甚至从其他进程启动)。如果您在主线程中初始化一个映射或列表来存储每个任务,那么您可以随时检查想要的 future 并在完成时获取其结果。

4

您可能希望使用“事件分派线程”来处理大部分事件驱动的事务。如果您正在使用Swing,则可以:

    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            Your code here.
        }
    });

或者创建一个实现Runnable接口的类,并将其传递到invokeLater()方法中。

2

如果您正在使用JavaFX,我强烈推荐,那么您可以使用

    Platform.runLater(new Runnable() {
        @Override
        public void run() {
            alert(text);
        }
    });

你可以在非UI线程内部创建一个Runnable对象,当你的线程返回时,该runnable将在UI线程中执行。


0
有点晚了,但我认为我的方法有点不同。
稍微修改一下Affe的解决方案。
 public static final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

  public static void main(String[] args) {

    Thread myThread = new Thread(

        () -> {
          String name = Thread.currentThread().getName();
          System.out.println("initial current thread " + name);

          queue.add(() -> System.out.println(Thread.currentThread().getName()));
        });

    myThread.setName("background thread");
    myThread.start();

    try {
      myThread.join();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    while (!queue.isEmpty()) {
      try {
        queue.take().run();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

输出

initial current thread background thread
main

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