基本上,我希望Aktor安全地更改scalafx-GUI。
我已经读过很多描述此问题的文章,但是那里有时有些矛盾并且已有几年历史,所以其中一些可能已经过时了。 我有一个有效的示例代码,我基本上想知道这种编程是否是线程保存的。 另一个问题是,是否可以通过某种方式配置sbt或编译器或其他某种方式,即所有线程(来自gui,actor和Futures)都由同一调度程序启动。
我在github上找到了一些示例代码“ scalafx-akka-demo”,其中已经有4岁了。在下面的示例中,我所做的基本上是相同的,只是简化了一些以简化操作。
然后有一个斯卡拉特克斯示例,其年龄大致相同。这个例子真的让我担心。 里面有一个来自维克多·巴生(Viktor Klang)的自编写的调度程序,该调度程序是从2012年开始的,我不知道该如何进行这项工作或我是否真的需要它。问题是:此调度程序只是一种优化,还是我必须使用类似的东西来节省线程?
但是,即使我并不是绝对需要像scalatrix中那样的调度程序,也不是最好为aktor线程分配一个调度程序,为scalafx-event-threads分配一个调度程序。 (也许对于期货线程来说也很好吗?)
在我的实际项目中,我有一些通过TCP-IP从设备传来的测量值,然后传给TCP-IP actor,并显示在scalafx-GUI中。但这很长。
这是我的示例代码:
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scalafx.Includes._
import scalafx.application.{JFXApp, Platform}
import scalafx.application.JFXApp.PrimaryStage
import scalafx.event.ActionEvent
import scalafx.scene.Scene
import scalafx.scene.control.Button
import scalafx.stage.WindowEvent
import scala.concurrent.ExecutionContext.Implicits.global
object Main extends JFXApp {
case object Count
case object StopCounter
case object CounterReset
val aktorSystem: ActorSystem = ActorSystem("My-Aktor-system") // Create actor context
val guiActor: ActorRef = aktorSystem.actorOf(Props(new GUIActor), "guiActor") // Create GUI actor
val button: Button = new Button(text = "0") {
onAction = (_: ActionEvent) => guiActor ! Count
}
val someComputation = Future {
Thread.sleep(10000)
println("Doing counter reset")
guiActor ! CounterReset
Platform.runLater(button.text = "0")
}
class GUIActor extends Actor {
def receive: Receive = counter(1)
def counter(n: Int): Receive = {
case Count =>
Platform.runLater(button.text = n.toString)
println("The count is: " + n)
context.become(counter(n + 1))
case CounterReset => context.become(counter(1))
case StopCounter => context.system.terminate()
}
}
stage = new PrimaryStage {
scene = new Scene {
root = button
}
onCloseRequest = (_: WindowEvent) => {
guiActor ! StopCounter
Await.ready(aktorSystem.whenTerminated, 5.seconds)
Platform.exit()
}
}
}
因此,此代码调出一个按钮,每次单击该按钮时,按钮的数量都会增加。一段时间后,按钮上的数字将被重置一次。
在此示例代码中,我尝试使scalafx-GUI,actor和Future相互影响。因此,单击按钮会向actor发送一条消息,然后actor更改GUI,这就是我在此处测试的内容。 未来还会发送给演员并更改GUI。
到目前为止,该示例有效,但我还没有发现每个问题都出问题了。 但是不幸的是,当涉及到线程节省时,这并不意味着什么
我的具体问题是:
1。)在示例代码线程中是否可以更改gui的方法?
2。)可能有更好的方法吗?
3。)可以从同一调度程序启动不同的线程吗? (如果是,那么如何?)
答案 0 :(得分:1)
要解决您的问题:
1)是否可以在示例代码线程中保存更改gui的方法?
是的
ScalaFX 所在的JavaFX 通过坚持所有 GUI 交互都发生在 JavaFX Application Thread上来实现线程安全。 ( JAT ),它是在 JavaFX 初始化期间创建的( ScalaFX 会为您完成此工作)。在与 JavaFX / ScalaFX 交互的不同线程上运行的任何代码都将导致错误。
通过通过Platform.runLater
方法传递交互代码来确保GUI代码在 JAT 上执行,该方法在 JAT 上评估其参数。由于参数是按名称传递 的,因此不会在调用线程中对其进行求值。
因此,就 JavaFX 而言,您的代码是线程安全的。
但是,如果传递给Platform.runLater
的代码包含对在其他线程上维护的可变状态的任何引用,则仍然可能出现潜在的问题。
您有两个呼叫Platform.runLater
的电话。在其中的第一个(button.text = "0")
中,唯一可变的状态(button.text
)属于 JavaFX ,它将在 JAT 上进行检查和修改。 ,所以你很好。
在第二个调用(button.text = n.toString
)中,您传递的是相同的 JavaFX 可变状态(button.text
)。但是,您还将传递对n
线程的GUIActor
的引用。但是,此值是不可变,因此从其值来看没有线程问题。 (计数由 Akka GUIActor
类的上下文维护,唯一改变计数的交互是通过 Akka 的消息处理机制来保证的保持线程安全。)
也就是说,这里存在一个潜在的问题:Future
既重置计数(将在GUIActor
线程上发生),又将文本设置为"0"
(将发生在 JAT 上)。因此,这两个操作可能会以意外的顺序发生,例如button
的文本在实际重置计数之前被更改为"0"
。如果在用户单击按钮的同时发生这种情况,则会得到竞赛条件,并且可以想象显示的值可能最终与当前计数不匹配。
2)可能有更好的方法吗?
总有更好的方法! ;-)
说实话,鉴于这个小例子,没有很多进一步的改进要做。
我会尝试将与 GUI 的所有交互都保留在GUIActor
或Main
对象中,以简化线程和同步问题。
例如,返回上一个答案的最后一点,而不是进行Future
更新button.text
,最好将其作为CounterReset
的一部分进行GUIActor
中的消息处理程序,然后保证计数器和button
外观正确同步(或至少始终以相同顺序更新),并保证显示值匹配计数。
如果您的GUIActor
类正在处理与 GUI 的大量交互,那么您可以让它在 JAT 上执行所有代码(我认为这是Viktor Klang的示例的目的),它将简化许多代码。例如,您不必调用Platform.runLater
与 GUI 进行交互。缺点是您不能与 GUI 并行执行处理,因此可能会降低其性能和响应速度。
3)可以从同一调度程序启动不同的线程吗? (如果是,那么如何?)
您可以为期货和 Akka 参与者指定自定义的执行上下文,以更好地控制其线程和调度。但是,考虑到Donald Knuth的观察,“过早的优化是万恶之源”,没有证据表明这将给您带来任何好处,结果您的代码将变得更加复杂。
据我所知,您不能更改 JavaFX / ScalaFX 的执行上下文,因为必须创建 JAT 精细控制,以确保线程安全。但是我可能是错的。
无论如何,拥有不同调度员的开销不会很高。使用期货和参与者的原因之一是,默认情况下,他们将为您解决这些问题。除非您有充分的理由这样做,否则我将使用默认值。