我如何为 Scalafx 编写保存 GUI-Aktor?

How can I write a save GUI-Aktor for Scalafx?

基本上我想要一个 Aktor 来安全地更改 scalafx-GUI。

我读过很多描述这个的帖子,但有些地方有时会相互矛盾并且有些年代久远,所以其中一些可能已经过时了。 我有一个有效的示例代码,我基本上想知道这种编程是否是线程保存的。 另一个问题是,如果我可以配置 sbt 或编译器或某种方式,所有线程(来自 gui、actors 和 futures)都由同一个调度程序启动。

我在 github 上找到了一些示例代码 "scalafx-akka-demo",它已有 4 年历史了。我在下面的例子中所做的基本上是一样的,只是为了简单起见进行了一些简化。

然后是年代差不多的scalatrix-example。这个例子真让我担心。 从 2012 年开始,Viktor Klang 有一个自写的调度程序,我不知道如何让它工作,或者我是否真的需要它。问题是:这个调度器只是一个优化还是我必须使用类似的东西来保存线程?

但即使我并不像在 scalatrix 中那样绝对需要调度程序,为 aktor-threads 和 scalafx-event-threads 使用一个调度程序也不是最佳选择。 (也许还有一个用于 Futures 线程?)

在我的实际项目中,我有一些测量值来自通过 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 相互影响。所以点击按钮会向演员发送一条消息,然后演员会更改 gui - 这就是我在这里测试的内容。 Future 也发送给 actor 并更改 gui。

到目前为止,这个例子是有效的,我还没有发现它有任何问题。 但不幸的是,当谈到节省线程时,这并不意味着什么

我的具体问题是:

1.)示例代码线程中更改gui的方法是否保存?

2.) 有没有更好的方法?

3.) 可以从同一个调度程序启动不同的线程吗? (如果是,那么如何?)

解决您的问题:

1) Is the method to change the gui in the example code thread save?

是的。

JavaFXScalaFX 所在,通过坚持所有 GUI 实现线程安全交互发生在 JavaFX 应用程序线程 (JAT) 上,它是在 JavaFX 初始化期间创建的 (ScalaFX 会为您解决这个问题)。与 JavaFX/ScalaFX 交互的不同线程上的任何代码 运行 都将导致错误。

您确保 GUI 代码在 JAT 上执行,方法是通过 Platform.runLater 方法传递交互代码,该方法在 上评估其参数JAT。因为参数是按名称传递的,它们不会在调用线程上计算。

因此,就 JavaFX 而言,您的代码是线程安全的。

但是,如果您传递给 Platform.runLater 的代码包含对其他线程上维护的可变状态的任何引用,则仍然会出现潜在问题。

您有两次呼叫 Platform.runLater。在第一个 (button.text = "0"),唯一可变状态 (button.text) 属于 JavaFX,将在 上检查和修改JAT,所以你很好

在第二次调用 (button.text = n.toString) 中,您将传递相同的 JavaFX 可变状态 (button.text)。但是您还传递了对 n 的引用,它属于 GUIActor 线程。然而,这个值是 不可变的 ,因此从它的值来看没有线程问题。 (计数由 Akka GUIActor class 的上下文维护,唯一改变计数的交互来自 Akka的消息处理机制,保证线程安全。)

也就是说,这里有一个潜在的问题:Future 既重置了计数(这将发生在 GUIActor 线程上),也将文本设置为 "0" (这将发生在 JAT 上)。因此,这两个操作可能会以意外的顺序发生,例如 button 的文本被更改为 "0" 计数实际重置之前。如果这与用户单击按钮同时发生,您将获得 竞争条件,并且可以想象显示的值最终可能与当前计数不匹配。

2) Is there may be a better way to do it?

总有更好的办法! ;-)

老实说,鉴于这个小例子,没有太多需要进一步改进的地方。

我会尝试将与 GUI 的所有交互保留在 GUIActorMain 对象中,以简化线程和同步问题.

例如,回到上一个答案的最后一点,而不是 Future 更新 button.text,如果将其作为 [=29] 的一部分进行更新会更好=] GUIActor 中的消息处理程序,然后保证计数器和 button 外观正确同步(或者,至少,它们总是以相同的顺序更新),并保证显示值匹配计数。

如果您的 GUIActor class 正在处理与 GUI 的大量交互,那么您可以让它在 JAT(我认为这是 Viktor Klang 示例的目的),这将简化它的很多代码。例如,您不必调用 Platform.runLater 来与 GUI 交互。缺点是您无法与 GUI 并行执行处理,这可能会降低其性能和响应速度。

3) Can the different threads be started from the same dispatcher? (if yes, then how?)

您可以为 future 和 Akka actor 指定自定义 执行上下文 以更好地控制它们的线程和调度。但是,根据 Donald Knuth 的观察 "premature optimization is the root of all evil",没有证据表明这会给您带来任何好处,并且您的代码会因此变得更加复杂。

据我所知,您无法更改 JavaFX/ScalaFX 的执行上下文,因为 JAT创建必须精细控制才能保证线程安全。但我可能是错的。

无论如何,拥有不同调度程序的开销不会很高。使用 futures 和 actors 的原因之一是它们会默认为你处理这些问题。除非你有充分的理由不这样做,否则我会使用默认值。