En gros, je veux un Aktor pour modifier une interface graphique scalafx en toute sécurité.
J'ai lu de nombreux articles décrivant ce sujet, mais ils sont parfois contradictoires et datent de plusieurs années, donc certains d'entre eux peuvent être dépassés. J'ai un code d'exemple fonctionnel et je veux essentiellement savoir si ce type de programmation permet d'économiser des threads. L'autre question est de savoir si je peux configurer sbt ou le compilateur ou autre de manière à ce que tous les threads (de l'interface, des acteurs et des futurs) soient lancés par le même dispatcher.
J'ai trouvé un exemple de code "scalafx-akka-demo" sur github, qui a 4 ans. Ce que j'ai fait dans l'exemple suivant est fondamentalement le même, juste un peu simplifié pour garder les choses faciles.
Ensuite, il y a l'exemple de scalatrix approximativement avec le même âge. Cet exemple m'inquiète vraiment. Il contient un répartiteur auto-écrit par Viktor Klang en 2012, et je n'ai aucune idée de la façon de le faire fonctionner ou si j'en ai vraiment besoin. La question est la suivante : ce répartiteur est-il seulement une optimisation ou dois-je utiliser quelque chose comme lui pour être thread save ?
Mais même si je n'ai pas absolument besoin du répartiteur comme dans scalatrix, il n'est pas optimal d'avoir un répartiteur pour les aktor-threads et un pour les scalafx-event-threads. (Et peut-être aussi un pour les Futures-threads).
Dans mon projet actuel, j'ai des valeurs de mesure provenant d'un dispositif via TCP-IP, allant vers un acteur TCP-IP et devant être affichées dans une interface utilisateur scalafx. Mais c'est beaucoup trop long.
Voici donc mon exemple de code :
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()
}
}
}
Ce code fait donc apparaître un bouton, et chaque fois que l'on clique dessus, le numéro du bouton augmente. Après un certain temps, le numéro du bouton est réinitialisé une fois.
Dans cet exemple de code, j'ai essayé d'amener le scalafx-GUI, l'acteur et le futur à s'influencer mutuellement. Donc le clic sur le bouton envoie un message à l'acteur, et ensuite l'acteur change le gui - ce qui est ce que je teste ici. Le Future envoie également un message à l'acteur et modifie le gui.
Jusqu'à présent, cet exemple fonctionne et je n'ai rien trouvé à redire. Mais malheureusement, lorsqu'il s'agit d'économiser du fil, cela ne signifie pas grand-chose.
Mes questions concrètes sont les suivantes :
1.) La méthode pour changer le gui dans le code d'exemple est-elle thread save ?
2.) Existe-t-il une meilleure façon de procéder ?
3.) Les différents threads peuvent-ils être lancés à partir du même dispatcher ? (si oui, alors comment ?)