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

时间:2019-05-16 10:27:41

标签: actor concurrent.futures scalafx executioncontext

基本上,我希望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。)可以从同一调度程序启动不同的线程吗?  (如果是,那么如何?)

1 个答案:

答案 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 的所有交互都保留在GUIActorMain对象中,以简化线程和同步问题。

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

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

  

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

您可以为期货和 Akka 参与者指定自定义的执行上下文,以更好地控制其线程和调度。但是,考虑到Donald Knuth的观察,“过早的优化是万恶之源”,没有证据表明这将给您带来任何好处,结果您的代码将变得更加复杂。

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

无论如何,拥有不同调度员的开销不会很高。使用期货和参与者的原因之一是,默认情况下,他们将为您解决这些问题。除非您有充分的理由这样做,否则我将使用默认值。