如何用Java编写这个等效代码?

时间:2017-03-20 15:43:01

标签: java multithreading future

我有一个异步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 ++代码?

3 个答案:

答案 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;相反,使用RunnableCallable来表示工作单元,让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);
    }
}

CallableRunnable是单独的接口,因此无法向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线程中运行,那么就不需要告知它已经完成,你应该能够继续执行超过这一点。