如何在 Java 中编写此等效代码?
How can I write this equivalent code in Java?
我有一个异步 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++ 代码
这是一个可能的解决方案:
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);
}
}
Callable
和 Runnable
是独立的接口,因此无法将 Callable
对象提交给 Platform.runLater()
,除非您重新包装 Callable
] 在另一个 Runnable
中,但那是荒谬的。调用 future.get()
将导致 Future
被评估(同时阻止执行,这可能是也可能不是你想要的)。如果您在 Callable
中省略了 try/catch,您必须在调用 future.get()
时处理它。但是,如果您不需要返回结果(因为您要返回 null
),您可以直接直接传递 运行nable:
public static void runAndWait(Runnable runner) {
try{
Platform.runLater(runner);
} catch (Exception ex) {
// handle exceptions; generic Exception used for brevity
}
}
但是这个版本不允许您明确决定何时执行任务运行;它会在 Platform.runLater()
决定 运行 时执行:
Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted. A runnable passed into the runLater method will be executed before any Runnable passed into a subsequent call to runLater. If this method is called after the JavaFX runtime has been shutdown, the call will be ignored: the Runnable will not be executed and no exception will be thrown.
直觉上,我认为这意味着第一个 Runnable 几乎立即执行,随后的 Runnable 在每个前一个 Runnable 完成时执行,但我不知道这一点。
评论后,还有一种可能:
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 线程中 运行ning,那么就没有必要通知它已完成,你应该能够继续执行到那个点之后。
你关注的是错误的事情。尽管 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
;相反,使用 Runnable
或 Callable
来表示工作单元,并让 ExecutorService
提供合适的 Future
.
或者,如果您愿意稍微远离 C++ 模型,并且不需要在特定线程上进行 运行 的工作,那么您可以考虑跳过 ExecutorService
并使用 SwingWorker
.
这个问题似乎类似于:
- Return result from javafx platform runlater
您问题中的 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());
在对其他答案的评论中,我注意到您也关注异常处理。您会注意到 FutureTask 的逻辑为 setException()
:
Causes this future to report an ExecutionException with the given throwable as its cause, unless this future has already been set or has been cancelled.
This method is invoked internally by the run()
method upon failure of the computation.
由于内部实现调用 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<Void>
,因为我对传递 Future 调用的任何数据结果不感兴趣。如果我对获得结果感兴趣,那么我可以使用 Future<SomeObjectType>
并使用 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 之间存在交互机制,详见相关(但不同)问题:
- JavaFX2: Can I pause a background Task / Service?
我有一个异步 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++ 代码这是一个可能的解决方案:
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);
}
}
Callable
和 Runnable
是独立的接口,因此无法将 Callable
对象提交给 Platform.runLater()
,除非您重新包装 Callable
] 在另一个 Runnable
中,但那是荒谬的。调用 future.get()
将导致 Future
被评估(同时阻止执行,这可能是也可能不是你想要的)。如果您在 Callable
中省略了 try/catch,您必须在调用 future.get()
时处理它。但是,如果您不需要返回结果(因为您要返回 null
),您可以直接直接传递 运行nable:
public static void runAndWait(Runnable runner) {
try{
Platform.runLater(runner);
} catch (Exception ex) {
// handle exceptions; generic Exception used for brevity
}
}
但是这个版本不允许您明确决定何时执行任务运行;它会在 Platform.runLater()
决定 运行 时执行:
Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted. A runnable passed into the runLater method will be executed before any Runnable passed into a subsequent call to runLater. If this method is called after the JavaFX runtime has been shutdown, the call will be ignored: the Runnable will not be executed and no exception will be thrown.
直觉上,我认为这意味着第一个 Runnable 几乎立即执行,随后的 Runnable 在每个前一个 Runnable 完成时执行,但我不知道这一点。
评论后,还有一种可能:
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 线程中 运行ning,那么就没有必要通知它已完成,你应该能够继续执行到那个点之后。
你关注的是错误的事情。尽管 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
;相反,使用 Runnable
或 Callable
来表示工作单元,并让 ExecutorService
提供合适的 Future
.
或者,如果您愿意稍微远离 C++ 模型,并且不需要在特定线程上进行 运行 的工作,那么您可以考虑跳过 ExecutorService
并使用 SwingWorker
.
这个问题似乎类似于:
- Return result from javafx platform runlater
您问题中的 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());
在对其他答案的评论中,我注意到您也关注异常处理。您会注意到 FutureTask 的逻辑为 setException()
:
Causes this future to report an ExecutionException with the given throwable as its cause, unless this future has already been set or has been cancelled. This method is invoked internally by the
run()
method upon failure of the computation.
由于内部实现调用 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<Void>
,因为我对传递 Future 调用的任何数据结果不感兴趣。如果我对获得结果感兴趣,那么我可以使用 Future<SomeObjectType>
并使用 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 之间存在交互机制,详见相关(但不同)问题:
- JavaFX2: Can I pause a background Task / Service?