After working some while with JavaFX (Java8) I had found the concept of Properties very useful, allowing to use bean compliant variables to be bound to update on changes usi
I ran into this today and it sure was helpful that someone pointed to threads. The errors caused by this can be insidious.
I'm using this solution extensively. You can't bindBidirectional with a ThreadSafeObjectProperty, but you can bind to an FX Property and update it using ThreadSafePropertySetter.
The setters return a Future. Which can be used to control the race conditions caused by Platform.runLater.
It's in Scala:
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
}
}
Typical usage:
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)
}
}
}