【问题标题】:How can I write this equivalent code in Java?如何在 Java 中编写此等效代码?
【发布时间】:2017-08-11 23:42:23
【问题描述】:

我有一个异步 C++ 函数,它需要将工作传递给另一个线程,然后等待该工作完成。我使用std::promise 对象完成了这项工作,如下所示:

void task(std::function<void()> const& func) {
    std::promise<void> promise;
    //You can think of 'thread_pool' as being a wrapper around a std::vector<std::thread>
    //where all the threads have a body that more-or-less look like
    /* void run() {
     *     while(running) {
     *         task t;
     *         if(task_queue.try_pop(t)) t();
     *     }
     * }
     */
    thread_pool.post([&] {
        try {
            func();
            promise.set_value();
        } catch (...) {
            promise.set_exception(std::current_exception());
        }
    });

    promise.get_future().get();
}

所以我的问题是,在 Java 中表达相同概念的最简单方法是什么?在我的具体情况下,我需要管理 Swing 线程和 JavaFX 线程之间的通信,并管理两者之间的任务。这是我目前所拥有的:

public static void runAndWait(Runnable runner) {
    Future<Object> future = new FutureTask<>(new Callable<Object>() {
        public Object call() {
            try {
                runner.run();
            } catch (RuntimeException e) {
                //??? How do I report the exception to the future?
            }
            return null;
        }
    });

    Platform.runLater(/*How do I run the future I've just created?*/);

    future.get();//I want the exception to throw here if we caught one.
}

不过,很明显,我遗漏了一些东西。如何表达我用Java描述的C++代码?

【问题讨论】:

  • @Baummitaugen 为什么删除了 c++ 标签?回答这个问题需要熟悉问题前半部分中描述的 C++ 代码。
  • 我会删除 c++ 代码,只包含你拥有的 java,描述你想要它做什么,以及它当前在做什么。那将是一个更高质量的问题
  • @Xirema 因为问题不在于 C++。对于诸如 “我如何在 C++ 中做 foo?” 之类的问题,它不会帮助任何人。纯粹是关于如何在 Java 中做 foo。

标签: java multithreading future


【解决方案1】:

这是一个可能的解决方案:

public static void runAndWait(Runnable runner) {
    Future<Object> future = new FutureTask<>(new Callable<Object>() {
        public Object call() {
            runner.run();
            return null;
        }
    });

    try {
        future.get(); // exception handling happens here.
    } catch (InterruptedException ex) {
        Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, ex);
    } catch (ExecutionException ex) {
        Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, ex);
    }
}

CallableRunnable 是独立的接口,因此无法将Callable 对象提交给Platform.runLater(),除非您将Callable 重新包装在另一个Runnable 中,但这很荒谬.调用 future.get() 将导致对 Future 进行评估(同时阻止执行,这可能是也可能不是您想要的)。如果你在Callable 中省略了try/catch,你必须在调用future.get() 时处理它。但是,如果您不需要返回结果(因为您返回的是 null),您可以直接传递可运行对象:

public static void runAndWait(Runnable runner) {
    try{
        Platform.runLater(runner);
    } catch (Exception ex) {
        // handle exceptions; generic Exception used for brevity
    }
}

但是,此版本不允许您明确决定何时运行任务;它会在Platform.runLater() 决定运行它时执行:

在未来某个未指定的时间在 JavaFX 应用程序线程上运行指定的 Runnable。此方法可以从任何线程调用,它将 Runnable 发布到事件队列,然后立即返回给调用者。 Runnables 按照它们发布的顺序执行。传递给 runLater 方法的 runnable 将在传递给后续调用 runLater 的任何 Runnable 之前执行。如果在 JavaFX 运行时关闭后调用此方法,则该调用将被忽略:不会执行 Runnable,也不会引发异常。

直觉上,我认为这意味着第一个 Runnable 几乎立即执行,随后的 Runnable 将在前一个 Runnable 完成时执行,但我不知道第一手资料。


在cmets之后,还有一种可能:

public static void runAndWait(Runnable runner) throws Exception {
    ExecutorService exec = Executors.newCachedThreadPool();
    exec.submit(runner);
    exec.shutdown();
    exec.awaitTermination(3l, TimeUnit.SECONDS);
}

这将让您设置等待时间的超时,可以任意短或长(本例中为 3 秒)。这将阻止执行,直到Runnable 线程完成,因此如果它在 FX 线程中运行,则无需发送消息表明它已完成,您应该能够在该点之后继续执行。

【讨论】:

  • 这些解决方案都不满足我正在使用的限制条件。我特别需要的是我的Runnable 在JavaFX 线程上运行,并且我当前的线程等待它完成。因此,我首先使用了期货。
  • @Xirema,如果您打算一直等到计算完成,那么使用Future 有什么意义?
  • @JohnBollinger 因为我需要一种机制来通知原始线程任务已完成。
  • @NAMS 这是同样的问题:你的最后一个例子没有把runner 放在JavaFX 线程上,它只是把它放在一个新的线程池上。代码需要在 JavaFX 线程上运行,否则由于 JavaFX 中的并发限制,它直接无法工作。
  • 如果线程池在FX线程上,会在等待终止时被阻塞。但是如果你需要 FX 线程等待,为什么不从一开始就同步运行代码呢?
【解决方案2】:

你专注于错误的事情。尽管Future 可以支持您想要的行为——等待计算完成——这只是普通方法调用的语义。 Future 的意义在于表示异步任务的完成,您可以在完成其他工作后稍后检查/检索该任务。您不需要执行Future 的整个合约。

您似乎正在努力解决的主要问题是如何确定 JavaFX 线程何时完成执行计算。据我所知或可以确定,JavaFX 没有专门的接口。它是围绕JavaFX是应用程序的整体管理线程的概念设计的。如果您想在该线程上执行工作并在完成时收到通知,那么执行通知需要成为工作的一部分。

例如,

public static void runAndWait(Runnable runner) {
    final SynchronousQueue<RuntimeException> exceptionQueue = new SynchronousQueue<>();

    Platform.runLater(
        // This Runnable wrapper performs synchronization with the invoking
        // thread via the SynchonousQueue
        new Runnable {
            public void run() {
                try {
                    runner.run();
                    exceptionQueue.put(null);
                } catch (RuntimeException re) {
                    exceptionQueue.put(re);
                }
            }
        });

    // blocks until an element is inserted into the queue:
    RuntimeException re = exceptionQueue.take();

    if (re != null) {
        throw new RuntimeException(re);
    }
}

你做错了。 开始与作业提交界面;相应的 Java 接口是ExecutorService。 Java 有多种实现,包括线程池的两种变体,以及一种您应该能够自定义以在您选择的现有线程上运行任务的抽象实现。您可能根本不需要实现Future;相反,使用RunnableCallable 来表示工作单元,并让ExecutorService 提供合适的Future

或者,如果您愿意稍微远离您的 C++ 模型,并且不需要在特定线程上运行该工作,那么您可以考虑跳过 ExecutorService 并使用 SwingWorker .

【讨论】:

  • 需要运行的代码需要在 JavaFX 线程上运行。因此我使用Platform.runLater。但无论出于何种原因,Platform 类没有 runAndWait 函数,就像 Swing 拥有 invokeLaterinvokeAndWait 一样。
  • @Xirema,我已经用另一种方法完全重写了这个答案,以实现你所追求的目标,包括处理RuntimeExceptions。
  • @JohnBollinger :这看起来不错...为了处理异常并以类似未来的方式工作,我会使用 CompletableFuture 和 complete/completeExceptionaly 构造...
  • 这看起来更接近我的需要。我会试试看它是否适合我。
  • @GPI,当然可以使用CompletableFuture 来完成,但是 IMO,任何类型的Future 都是这项工作的错误工具,正如我试图在答案开头解释的那样.无论如何,这并不能解决等待另一个线程完成手头任务的根本问题。因此,您可以将CompletableFuture 包裹在我建议的实现或类似的实现周围,但如果有的话,您不会获得太多收益。
【解决方案3】:

这个问题似乎类似于:

您问题中的runAndWait 代码看起来与 Sarcan 的答案非常相似,即:

final FutureTask query = new FutureTask(new Callable() {
    @Override
    public Object call() throws Exception {
        return queryPassword();
    }
});
Platform.runLater(query);
System.out.println(query.get());

在其他答案的 cmets 中,我注意到您也关心异常处理。你会注意到 FutureTask 对setException() 有逻辑:

导致这个未来报告一个以给定的 throwable 作为其原因的 ExecutionException,除非这个未来已经被设置或被取消。 此方法在计算失败时由run() 方法在内部调用。

由于内部实现调用setException 调用,您不需要显式调用setException。在 FutureTask 的上下文中抛出的任何未捕获的异常都将设置在该 FutureTask 中,您可以通过从您的 future.get() 调用中捕获 ExecutionException 来解释它。

// non-JavaFX thread code...

Future<Void> future = new FutureTask<>(() -> {
        // work to be done on the JavaFX thread...
        return null;
    }
});

// do the work on the JavaFX thread.
Platform.runLater(future);

try {
    // await completion of the work on the JavaFX thread.
    future.get();
} catch (InterruptedException ex) {
    // restore the interrupt status (see the linked Goetz article).
    Thread.currentThread().interrupt();
} catch (ExecutionException ex) {
    // exception handling logic for an exception occurring 
    // in the body of the FutureTask here.
}

在上面的示例中,我有一个Future&lt;Void&gt;,因为我对传递来自 Future 调用的任何数据结果不感兴趣。如果我有兴趣获得结果,那么我可以使用Future&lt;SomeObjectType&gt; 并拥有SomeObjectType result = future.get()。在这种情况下,我喜欢尽可能多地使用immutable objects(例如SomeObjectType),尽管对于future.get() 场景并不是绝对必要的,因为它本质上是按顺序访问对象,而不是跨线程并行访问.

如果您想在非 JavaFX 线程上重新抛出 JavaFX 应用程序线程上发生的异常,那么您可以这样做:

} catch (ExecutionException ex) {
    throw ex.getCause();
}

以下信息是针对不同线程与JavaFX(或一般Java)的交互,与问题没有直接关系,所以回答问题的细节可以忽略,它只是作为背景信息。

一些背景信息:我发现一个非常出色的关于在 Java 中实现任务的文章是 Brian Goetz 的文章:

相反的交互: 上面给出的示例涉及从另一个线程调用 JavaFX 应用程序线程上的任务并等待它们完成。如果您遇到相反的情况,您想在另一个线程而不是 JavaFX 线程上调用任务,那么您将使用 JavaFX Task。在这种情况下,您不希望 JavaFX 线程等待非 JavaFX 任务的完成,因为您不应该暂停或挂起 JavaFX 线程(相反,必须同时执行任务调用,如何执行此操作在链接中进行了说明任务 Javadoc)。

在相关(但不同)的问题中详细介绍了后台线程和 JavaFX UI 之间的交互机制:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多