爲什麼在調度任務之前調用call()方法?
TLDR;版本號:不是。只是在您收到通知它已安排之前調用它。
您有兩個線程運行,實質上是獨立的:您明確創建的線程和FX應用程序線程。當你啓動你的應用程序線程時,它將在該線程上調用Taskus.call()
。但是,通過調用Platform.runLater(...)
,可以在FX應用程序線程中對任務屬性進行更改。
所以,當你在你的線程中調用start()
,會發生以下情況幕後:
- 一個新的線程啓動
- 在該線程,在
Task
內部call()
方法被調用。該方法:
- 時刻表可運行對FX應用程序線程執行,改變任務的
stateProperty
到SCHEDULED
- 時刻表可運行對FX應用程序線程執行,改變任務的
stateProperty
到RUNNING
- 調用你的
call
方法
當FX應用程序線程接收改變任務的狀態從READY
到SCHEDULED
可運行的,後來從SCHEDULED
到RUNNING
,它會影響這些更改並通知任何聽衆。由於這與call
方法中的代碼位於不同的線程,因此您的call
方法中的代碼與您的stateProperty
聽衆中的代碼之間沒有「發生之前」關係。換句話說,不能保證首先會發生什麼。特別是,如果FX應用程序線程已經忙於做某事(呈現UI,處理用戶輸入,處理其他Runnable
s傳遞給Platform.runLater(...)
等),它將在對任務stateProperty
進行更改之前完成這些操作。
什麼可以保證的是,改變SCHEDULED
和RUNNING
將在FX應用程序線程計劃(但不一定是執行)您call
方法之前被調用,並以SCHEDULED
變化會前執行執行RUNNING
的更改。
下面是一個比喻。假設我接受客戶的請求來編寫軟件。把我的工作流想象成後臺線程。假設我有一位管理員助理爲我與顧客進行溝通。把她的工作流想象成FX應用程序線程。因此,當我收到客戶的請求時,我會告訴我的管理員助理向客戶發送電子郵件並通知他們我收到了請求(SCHEDULED
)。我的管理員助理忠實地將其放在她的「待辦事項」清單上。不久之後,我告訴我的管理員助理向客戶發送電子郵件,告訴他們我已經開始致力於他們的項目(RUNNING
),並將其添加到她的「待辦事項」列表中。然後我開始研究這個項目。我在這個項目上做了一些工作,然後進入Twitter併發布一條推文(你的System.out.println("Some code already executed")
)「爲xxx工作,這真的很有趣!」。根據我助手的「待辦事項」列表中已有的事情數量,在將電子郵件發送給客戶之前,推文很可能會出現,因此客戶很可能會看到我已經在看到該項目之前開始工作即使從我的工作流程的角度來看,即使從工作流程的角度來看,所有事情都按照正確的順序進行。
這通常是您想要的:status屬性旨在用於更新UI,因此它必須在FX應用程序線程上運行。既然你在不同的線程上運行你的任務,你可能希望它做到這一點:運行在另一個執行線程中。
在調用方法實際開始執行之後,我認爲不可能在預定狀態的變化中觀察到大量時間(多於一幀渲染脈衝,通常爲1/60秒):如果發生這種情況您可能會阻止FX應用程序線程以防止它看到這些更改。在你的例子中,時間延遲顯然是最小的(小於一毫秒)。
如果您想在任務開始時執行某些操作,但不關心執行哪個線程,請在調用方法開始時執行此操作。 (就上面的類比而言,這相當於我將電子郵件發送給客戶,而不是要求我的助理這樣做。)
如果您確實需要在您的調用方法中的代碼發生在某個用戶通知已經發生的FX應用程序線程,則需要使用以下模式:
public class Taskus extends Task<Void> {
@Override
public Void call() throws Exception {
FutureTask<Void> uiUpdate = new FutureTask<Void>(() -> {
System.out.println("Task has started");
// do some UI update here...
return null ;
});
Platform.runLater(uiUpdate);
// wait for update:
uiUpdate.get();
for (int i = 0; i < 10000; i++) {
// any VM implementation worth using is going
// to ignore this loop, by the way...
}
System.out.println("Some code already executed." + " at " + (System.currentTimeMillis()-start));
Thread.sleep(3000);
return null ;
}
}
在這個例子中,你肯定可以看到「任務已啓動」之前看到「已執行的一些代碼」。此外,由於顯示「任務已啓動」方法發生在同一個線程(FX應用程序線程)上,因爲狀態變化爲SCHEDULED
和RUNNING
,並且由於在狀態變化之後顯示「任務已啓動」消息被安排,在看到「任務已啓動」消息之前,您可以保證看到SCHEDULED
和RUNNING
的轉換。 (就類比而言,這與我請助理髮送電子郵件一樣,然後在我知道她已發送郵件之前不會開始任何工作。)
另外請注意,如果您更換原有的呼叫
System.out.println("Some code already executed." + " at " + (System.currentTimeMillis()-start));
與
Platform.runLater(() ->
System.out.println("Some code already executed." + " at " + (System.currentTimeMillis()-start)));
,那麼你也保證看到的順序調用你期望:
SCHEDULED after 5 milliseconds
RUNNING after 7 milliseconds
Some code already executed. after 8 milliseconds
SUCCEEDED after 3008 milliseconds
這最後一個版本與我的比喻類似,要求我的助理爲我發佈推文。