[英]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 ++代碼?
您正在專注於錯誤的事情。 盡管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
。
這個問題似乎類似於:
您問題中的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()
邏輯:
導致此將來以給定throwable作為其原因來報告ExecutionException,除非已經設置或取消了該將來。 計算失敗時,
run()
方法在內部調用此方法。
當內部實現調用setException
調用時,您無需顯式調用setException
。 在FutureTask上下文中引發的任何未捕獲的異常都將在該FutureTask中設置,您可以通過捕獲來自future.get()
調用中的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()
。 在這種情況下,我喜歡盡可能多地使用不可變的對象 (例如,對於SomeObjectType
),盡管對於future.get()
場景並不是嚴格必須的,因為這實際上是按順序訪問對象,而不是跨線程並行訪問對象。
如果要重新引發在非JavaFX線程上的JavaFX應用程序線程上發生的異常,則可以這樣做:
} catch (ExecutionException ex) {
throw ex.getCause();
}
以下信息用於與JavaFX(或Java)進行基於線程的不同交互,並且與問題沒有直接關系,因此對於回答問題的細節可以忽略,僅用作背景信息。
一些背景信息: Brian Goetz的文章在Java中實現任務方面非常出色:
相反的交互:上面給出的示例與從另一個線程調用JavaFX應用程序線程上的任務並等待其完成有關。 如果遇到相反的情況,您想在另一個線程而不是JavaFX線程上調用Task,則可以使用JavaFX Task 。 在這種情況下,您不希望JavaFX線程等待非JavaFX任務的完成,因為您永遠不要暫停或掛起JavaFX線程(相反,Task調用必須同時執行,鏈接中介紹了如何執行此操作任務Javadoc)。
在相關(但不同)的問題中詳細介紹了在后台線程和JavaFX UI之間進行交互的機制:
這是一個可能的解決方案:
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
),則可以直接將可運行對象立即傳遞:
public static void runAndWait(Runnable runner) {
try{
Platform.runLater(runner);
} catch (Exception ex) {
// handle exceptions; generic Exception used for brevity
}
}
但是,此版本無法讓您明確決定何時要運行任務。 每當Platform.runLater()
決定運行它時,它將執行:
將來在某些未指定的時間在JavaFX Application Thread上運行指定的Runnable。 可以從任何線程調用此方法,該方法會將Runnable張貼到事件隊列中,然后立即返回給調用者。 Runnable將按照它們的發布順序執行。 傳遞給runLater方法的runnable將在傳遞給后續對runLater的調用的任何Runnable之前執行。 如果在關閉JavaFX運行時之后調用此方法,則該調用將被忽略:Runnable將不會執行,並且不會引發任何異常。
憑直覺,我認為這意味着第一個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線程中運行,則無需通知它已完成,那么您應該能夠繼續執行超出此范圍的操作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.