我有一个异步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 ++代码?
答案 0 :(得分: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
;相反,使用Runnable
或Callable
来表示工作单元,让ExecutorService
提供合适的Future
。
或者,如果您愿意稍微远离C ++模型,并且如果您不需要在特定线程上运行工作,那么您可以考虑跳过{ {1}}并使用 SwingWorker
。
答案 1 :(得分:2)
这个问题似乎与:
相似您问题中的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()
的逻辑:
导致此未来报告ExecutionException,并将给定的throwable作为其原因,除非此未来已设置或已取消。 在计算失败时,
run()
方法在内部调用此方法。
当内部实现调用setException
调用时,您不需要显式调用setException
。 FutureTask上下文中抛出的任何未捕获的异常都将在FutureTask中设置,您可以通过从ExecutionException
调用中捕获future.get()
来解释它。
// 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)不同的基于线程的交互,并且与问题没有直接关系,因此可以忽略回答问题的细节,它仅用作背景信息。 / p>
一些背景信息:我发现在Java中任务的实现非常出色,是Brian Goetz的文章:
相反的交互:上面给出的示例涉及从另一个线程调用JavaFX应用程序线程上的任务并等待它们完成。如果您想要在另一个线程而不是JavaFX线程上调用Task,那么您将使用JavaFX Task。在这种情况下,您不希望JavaFX线程等待非JavaFX任务的完成,因为您永远不应该暂停或挂起JavaFX线程(而是必须同时执行Task调用,如何解释如何执行此操作链接的任务Javadoc)。
在相关(但不同)的问题中详细介绍了后台线程和JavaFX UI之间的交互机制:
答案 2 :(得分:0)
这是一个可能的解决方案:
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
),你可以直接传递runnable:
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将在任何Runnable传递给后续的runLater调用之前执行。如果在JavaFX运行时关闭后调用此方法,则将忽略该调用:将不会执行Runnable并且不会抛出异常。
直觉上,我认为这意味着第一个Runnable几乎立即被执行,后续的Runnables会在前一个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线程中运行,那么就不需要告知它已经完成,你应该能够继续执行超过这一点。