2015-10-15 70 views
2

根據文檔,「執行任務時調用Task#call()」。 考慮下面的程序:執行任務前調用的任務#call()方法

import javafx.application.Application; 
import javafx.concurrent.Task; 
import javafx.stage.Stage; 

public class TestTask extends Application { 

    Long start; 

    public void start(Stage stage) { 

     start = System.currentTimeMillis(); 

     new Thread(new Taskus()).start(); 
    } 

    public static void main(String[] args) { 
     launch(); 
    } 

    class Taskus extends Task<Void> { 

     public Taskus() { 
      stateProperty().addListener((obs, oldValue, newValue) -> { 
       try { 
        System.out.println(newValue + " at " + (System.currentTimeMillis()-start)); 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
      }); 
     } 

     public Void call() throws InterruptedException { 

      for (int i = 0; i < 10000; i++) { 
       // Could be a lot longer. 
      } 
      System.out.println("Some code already executed." + " at " + (System.currentTimeMillis()-start)); 

      Thread.sleep(3000); 

      return null; 
     } 
    } 
} 

執行這個程序給我下面的輸出:

Some code already executed. after 5 milliseconds 
SCHEDULED after 5 milliseconds 
RUNNING after 7 milliseconds 
SUCCEEDED after 3005 milliseconds 

爲什麼call()方法任務之前調用甚至計劃?這對我來說沒有意義。在我第一次看到這個問題的任務中,我的任務在任務進入SCHEDULED狀態之前執行了幾秒鐘。如果我想給用戶一些關於狀態的反饋,那麼什麼也沒有發生,直到任務已經執行了幾秒鐘?

回答

3

爲什麼在調度任務之前調用call()方法?

TLDR;版本號:不是。只是在您收到通知它已安排之前調用它。


您有兩個線程運行,實質上是獨立的:您明確創建的線程和FX應用程序線程。當你啓動你的應用程序線程時,它將在該線程上調用Taskus.call()。但是,通過調用Platform.runLater(...),可以在FX應用程序線程中對任務屬性進行更改。

所以,當你在你的線程中調用start(),會發生以下情況幕後:

  1. 一個新的線程啓動
  2. 在該線程,在Task內部call()方法被調用。該方法:
  3. 時刻表可運行對FX應用程序線程執行,改變任務的statePropertySCHEDULED
  4. 時刻表可運行對FX應用程序線程執行,改變任務的statePropertyRUNNING
  5. 調用你的call方法

當FX應用程序線程接收改變任務的狀態從READYSCHEDULED可運行的,後來從SCHEDULEDRUNNING,它會影響這些更改並通知任何聽衆。由於這與call方法中的代碼位於不同的線程,因此您的call方法中的代碼與您的stateProperty聽衆中的代碼之間沒有「發生之前」關係。換句話說,不能保證首先會發生什麼。特別是,如果FX應用程序線程已經忙於做某事(呈現UI,處理用戶輸入,處理其他Runnable s傳遞給Platform.runLater(...)等),它將在對任務stateProperty進行更改之前完成這些操作。

什麼可以保證的是,改變SCHEDULEDRUNNING將在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應用程序線程)上,因爲狀態變化爲SCHEDULEDRUNNING,並且由於在狀態變化之後顯示「任務已啓動」消息被安排,在看到「任務已啓動」消息之前,您可以保證看到SCHEDULEDRUNNING的轉換。 (就類比而言,這與我請助理髮送電子郵件一樣,然後在我知道她已發送郵件之前不會開始任何工作。)

另外請注意,如果您更換原有的呼叫

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 

這最後一個版本與我的比喻類似,要求我的助理爲我發佈推文。