2014-12-05 33 views
9

在使用JavaFX(Java8)工作了一段時間之後,我發現屬性的概念非常有用,允許使用bean兼容變量進行綁定,以使用計算樹更新更改例如:JavaFX屬性在GUI視圖範圍之外的可用性

class Person { 
     StringProperty name; 
     ... 
    } 

    Person owner; 
    Person human; 

    owner.name().bind(human.name()); 

允許將GUI控件綁定到'模型',以便在更改時自動更新。

所以我開始在模型中使用屬性(我的數據對象正在做我的功能操作)。但是JavaFX是一個單線程的GUI實現,並且只有在JavaFX線程中完成設置鏈接到某些GUI控件的屬性纔是允許的。否則一會拋出異常:

Exception in thread "Thread-5" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-5 

如果我現在開始編寫多線程代碼,我終於不能使用這些屬性,即使我很想。我無法在Platform.runLater()調用中封裝每個更改以將其傳遞給JavaFX線程。

爲什麼JavaFX不提供線程安全的屬性綁定? (或者是嗎?)

回答

29

的GUI視圖

JavaFX的性質可以確實地在GUI視圖的範圍之外使用的範圍之外的JavaFX屬性的可用性。基本的Oracle JavaFX binding tutorial通過創建一個簡單的非GUI Java程序演示了這一點,該程序表示一個Bill對象,其中賬單總額通過JavaFX屬性公開。

爲什麼JavaFX不提供線程安全的屬性綁定? (或者是否?)

JavaFX不支持線程安全屬性綁定。可能它不支持線程安全屬性綁定的原因是因爲它不需要。

儘管JavaFX在內部具有一個帶有渲染線程,應用程序線程等的多線程體系結構,但在開發者的外部,它實際上只公開單個應用程序線程。從開發人員的角度來看,開發人員將其應用程序編碼爲單線程系統。開發人員和內部JavaFX實現可以假定所有內容都在單線程環境中運行,編碼更加簡單(有關爲什麼會出現這種情況的一些背景信息,請參見Multithreaded toolkits: A failed dream?How to run two JavaFX UI in actual different threads)。知道它在這樣的環境中執行的屬性實現也可以採用單線程體系結構並跳過內建的線程安全控制。

JavaFX確實能夠爲concurrent tasks產生新線程(也可以使用標準Java開發的許多併發工具中的任何一個)。 JavaFX併發API確實能夠以線程安全的方式反饋屬性值(例如爲任務執行完成的工作的百分比),但這是以非常特定的方式完成的。任務API有專門的方法來修改這些屬性(例如updateProgress),並在內部使用線程檢查和調用(如Platform.runLater)來確保代碼以線程安全的方式執行。

因此,JavaFX併發實用程序不通過JavaFX屬性機制的內置功能實現線程安全,而是通過在併發實用程序實現中顯式檢查和調用一組非常特定且有限的屬性。即使這樣,用戶在使用這些實用程序時必須非常小心,因爲他們經常認爲他們可以執行諸如直接從其併發任務中修改JavaFX GUI stage屬性(即使文檔聲明不應該這樣做);如果使用標準Java併發實用程序(如java.util.concurrent包)而不是javafx.concurrent,則需要採取同樣的措施。

有人可能會創建JavaFX屬性機制的擴展,以便在線程安全的環境中更好地工作,但是迄今爲止沒有人創建這樣的擴展。

但是,JavaFX是一個單線程GUI實現,並且只有在JavaFX線程中完成設置鏈接到某些GUI控件的此類屬性纔是允許的。

問題的標題是「JavaFX屬性在GUI視圖範圍之外的可用性」,但是您描述的特定問題是一個相當特殊的子集,在這裏我認爲您具有以下組合:

  1. 模型類的屬性。
  2. 模型類中的屬性可以通過JavaFX應用程序線程或其他用戶線程讀取和寫入。
  3. 模型類中的屬性可以綁定到活動的GUI元素,例如TextField中的文本或屏幕上顯示的標籤。

開箱即可使用,因爲JavaFX系統假定JavaFX GUI組件的屬性僅在JavaFX應用程序線程中被讀取或修改。此外,在內部支持綁定和更改偵聽器,屬性本身需要維護偵聽器和綁定屬性的列表以進行修改,並且這些列表可能假定它們只能從單個線程訪問或修改。通過使用Platform.runLater將調用放在JavaFX應用程序線程上,通過確保每個讀取或寫入都在單個線程上執行,但您可以通過確保每個讀取或寫入都進行解決,但從您的問題來看,這正是您所使用的代碼試圖避免。

國際海事組織對於上述幾點中概述的用例,沒有其他解決方案,必須使用Platform.runLater包裝。通過爲屬性訪問和更新提供外觀方法(類似於JavaFX併發任務實現),您可能會隱藏runLater調用的一些複雜性和樣板,但這樣的系統實現起來可能有點複雜(特別是如果您想要爲整個屬性/綁定子系統實現一個通用的解決方案,而不是針對像Task這樣的一些特定屬性的專門解決方案)。

什麼是JavaFX屬性呢?

主要的現有用例是用於支持基於綁定的GUI編程模型的JavaFX GUI應用程序。 JavaFX屬性廣泛用於JavaFX API和使用該API的任何應用程序。

由於JavaFX現在包含在所有標準的新的Oracle JDK發行版中,您還可以使用非JavaFX程序的屬性。例如,有關如何在JPA entity beans for instance中使用這些屬性的討論。根據我的經驗,這些非JavaFX API用例目前非常罕見。

儘管JavaFX屬性和綁定包位於javafx包名稱空間內,但它們不依賴於其他JavaFX包。在將來的模塊化JDK中,比如Java 9被認爲是可能的,它將有可能具有依賴於JavaFX屬性和綁定模塊的Java程序,並且不具有用於JavaFX GUI開發的其他模塊的依賴性(這可能對於某些無頭服務器系統,它們是許多Java應用程序的主要部署目標)。

最初爲其他JavaFX設施(例如時間軸和轉換)設置了類似的設計,以便通過時間軸實時安排任務的實時Java系統可以使用JavaFX中的動畫/時間軸模塊,而無需依賴剩下的就是JavaFX系統(但我不確定原來的設計是否延續到今天,因此可能再也不可能了,動畫模塊的基本脈衝通常被鎖定在60fps的最小刻度上,可能鎖定在屏幕刷新率取決於實施)。

JavaFX屬性不是Java的屬性管理的通用解決方案,但它們可能是迄今爲止我所見過的最接近,最完整的解決方案。正如我記得JavaFX項目負責人Richard Bair指出的那樣,理想的是屬性功能將被內置到Java編程語言中,所以支持不僅來自API,還來自語言語法的改進。也許一些未來的Java版本如10+可能具有這些功能。當然,這是一個可能追溯到Java語言和JDK規範開始的舊討論。儘管如此,這個世界並不理想,並且JavaFX屬性機制(即使它具有龐大的語法和缺乏對線程安全的內置支持)仍然是許多應用程序的有用工具。還要注意的是,其他語言的擴展如Scala通過ScalaFX進行擴展,這使JavaFX屬性機制看起來像是該語言語法的一部分。

第三方庫(如EasyBind)增強了JavaFX屬性機制以更好地支持編程範例,如功能性反應式編程。現在,如果我想在JavaFX程序中廣泛使用屬性類型設施,我可能會將它基於JavaFX屬性和(可能)EasyBind和ReactFX的組合,因爲這似乎是最好的解決方案對於Java。

我不會在高度併發的環境中使用這樣的解決方案,由於缺乏基礎庫中的線程安全支持,線程是非分區化的。屬性基於對可變對象的副作用更改,在多線程程序中這是很難推理的。即使內部屬性實現已被修改爲允許對屬性進行線程安全讀/寫,但我不確定這是否是一種很好的方法。對於需要在併發子任務之間進行大量通信的高併發系統,使用諸如Actors(例如akka/erlang)或communicating sequential processes的另一編程範例可能比綁定屬性更合適。

+0

在其他的解決辦法是建立物業接口的自己的線程安全的實現。這是非常狹窄的,所以最大的努力將在於如何正確地做到這一點。 – dinvlad 2016-03-18 15:02:27

+0

@dinvlad我挑戰你爲JavaFX屬性提供線程安全的實現。 – jewelsea 2016-03-18 17:51:39

0

我今天遇到了這個問題,確實有人指出線程有幫助。由此造成的錯誤可能是陰險的。

我正在廣泛使用此解決方案。您無法使用ThreadSafeObjectProperty綁定雙向,但您可以綁定到FX Property並使用ThreadSafePropertySetter對其進行更新。

設置者返回一個未來。可以用來控制Platform.runLater引起的競爭條件。

它在斯卡拉:

class SafePublishProperty[T](init: T) { 
    val writable = new ReadOnlyObjectWrapper[T](init) 
    def readOnly: ObjectExpression[T] = writable.getReadOnlyProperty 
} 

class ThreadSafeBooleanProperty(init: Boolean) { 
    protected val property = new ReadOnlyBooleanWrapper(init) 

    def value: BooleanExpression = property.getReadOnlyProperty 

    def setValue(value: Boolean): Future[Boolean] = { 
    val promise = Promise[Boolean] 
    if (Platform.isFxApplicationThread) { 
     property.setValue(value) 
     promise.success(true) 
    } 
    else 
     try { 
     Platform.runLater(() => { 
      property.setValue(value) 
      promise.success(true) 
     }) 
     } catch { 
     case _: IllegalStateException => 
      property.setValue(value) 
      promise.success(true) 
     } 
    promise.future 
    } 
} 

class ThreadSafeObjectProperty[T](init: T) { 
    protected val property = new SafePublishProperty[T](init) 

    def value: ObjectExpression[T] = property.readOnly 

    def setValue(value: T): Future[Boolean] = { 
    val promise = Promise[Boolean] 
    if (Platform.isFxApplicationThread) { 
     property.writable.setValue(value) 
     promise.success(true) 
    } 
    else { 
     try { 
     Platform.runLater(() => { 
      property.writable.setValue(value) 
      promise.success(true) 
     }) 
     } catch { 
     case _: IllegalStateException => 
      property.writable.setValue(value) 
      promise.success(true) 
     } 
    } 
    promise.future 
    } 
} 

object ThreadSafePropertySetter { 
    def execute(function:() => Unit): Future[Boolean] = { 
    val promise = Promise[Boolean] 
    if (Platform.isFxApplicationThread) { 
     function.apply() 
     promise.success(true) 
    } 
    else { 
     try { 
     Platform.runLater(() => { 
      function.apply() 
      promise.success(true) 
     }) 
     } catch { 
     case ex: IllegalStateException => 
      function.apply() 
      promise.success(true) 
     } 
    } 
    promise.future 
    } 
} 

典型用途:

class SomeExample { 
    private val propertyP = new ThreadSafeBooleanProperty(true) 

    def property: BooleanExpression = propertyP.value 

    private class Updater extends Actor { 
     override def receive: Receive = { 
     case update: Boolean => 
      propertyP.setValue(update) 
     } 
    } 
    }