Как мне написать сохраненный GUI-Актор для Scalafx? - PullRequest
2 голосов
/ 16 мая 2019

По сути, я хочу, чтобы Актор безопасно изменил графический интерфейс scalafx.

Я читал много постов, описывающих это, но там, где иногда они противоречивы и некоторые годы, поэтому некоторые из них могут быть устаревшими.У меня есть рабочий пример кода, и я в основном хочу знать, является ли этот вид программирования с поддержкой потоков.Другой вопрос, могу ли я настроить sbt или компилятор или что-то таким образом, чтобы все потоки (из графического интерфейса, актеров и фьючерсов) запускались одним и тем же диспетчером.

Я нашел несколькоПример кода "scalafx-akka-demo" на github, которому 4 года.То, что я сделал в следующем примере, в основном то же самое, только немного упрощенное, чтобы упростить задачу.

Тогда есть пример скалярицы примерно того же возраста.Этот пример действительно беспокоит меня.Там есть самописный диспетчер из Виктора Кланга из 2012 года, и я понятия не имею, как сделать эту работу или она мне действительно нужна.Вопрос в том, является ли этот диспетчер только оптимизацией, или я должен использовать что-то подобное, чтобы сохранить поток?

Но даже если мне совсем не нужен диспетчер, как в scalatrix, он не оптимален дляесть диспетчер для потоков-акторов и один для потоков-событий скалафкса.(И, может быть, один для Futures-thread тоже хорошо?)

В моем реальном проекте у меня есть некоторые измерительные данные, поступающие с устройства через TCP-IP, идущие к актору TCP-IP и должны отображатьсяв скалярном 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()
    }
  }
}

Таким образом, этот код вызывает кнопку, и при каждом нажатии номер кнопки увеличивается.Через некоторое время номер на кнопке сбрасывается один раз.

В этом примере кода я пытался заставить скалярный GUI, актера и будущее влиять друг на друга.Таким образом, нажатие кнопки отправляет сообщение актеру, а затем актер меняет gui - это то, что я тестирую здесь.Будущее также отправляет актеру и изменяет графический интерфейс.

Пока этот пример работает, и я не нашел в этом ничего плохого.Но, к сожалению, когда дело доходит до сохранения потока, это не очень много значит

Мои конкретные вопросы:

1.) Можно ли сохранить метод изменения графического интерфейса в примере потока кода?

2.) Возможно, есть лучший способ сделать это?

3.) Можно ли запускать разные потоки из одного диспетчера?(если да, то как?)

1 Ответ

1 голос
/ 16 мая 2019

Чтобы ответить на ваши вопросы:

1) Способ изменения графического интерфейса в примере кода сохранить?

Да.

JavaFX , на котором сидит ScalaFX , обеспечивает безопасность потока, настаивая на том, чтобы все взаимодействия 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 контекст класса, и единственные взаимодействия, которые изменяют количество, происходят через Akka механизм обработки сообщений, который гарантированно безопасен для потоков.)

Тем не менее, есть одинПотенциальная проблема здесь: Future сбрасывает счетчик (который будет происходить в потоке GUIActor), а также устанавливает текст в "0" (который будет происходить в JAT ). Следовательно,возможно, что эти два действия произойдут в неожиданном порядке, например * 1Текст 074 * изменяется на "0" до , счет фактически сбрасывается.Если это происходит одновременно с нажатием кнопки пользователем, вы получите условие гонки и возможно, что отображаемое значение может в конечном итоге не соответствовать текущему количеству.

2) Может быть, есть лучший способ сделать это?

Всегда есть лучший способ!; -)

Если честно, учитывая этот небольшой пример, дальнейших улучшений не так много.

Я бы постарался сохранить все взаимодействие с GUI. внутри либо GUIActor, либо объекта Main для упрощения потоков и синхронизации.

Например, вместо возврата к Future, вернитесь к последней точке в предыдущем ответе.обновите button.text, было бы лучше, если бы это было сделано как часть обработчика сообщений CounterReset в GUIActor, который затем гарантирует, что счетчик и внешний вид button синхронизированы правильно (или, по крайней мере, они 'всегда обновляется в одном и том же порядке), причем отображаемое значение гарантированно соответствует количеству.

Если ваш класс GUIActor обрабатывает много взаимодействий с GUI , то вы можетепусть он выполнит весь свой код на JAT (я думаю, что это было целью примера Виктора Кланга), что упростило бы большую часть его кода.Например, вам не нужно было бы вызывать Platform.runLater для взаимодействия с GUI .Недостатком является то, что вы не сможете выполнять обработку параллельно с GUI , что может снизить его производительность и быстродействие в результате.

3) Можно ли запускать разные потокиот того же диспетчера?(если да, то как?)

Вы можете указать пользовательские контексты исполнения для фьючерсов и Akka актеров, чтобы лучше контролировать свои потоки и диспетчеризацию. Однако, учитывая наблюдение Дональда Кнута о том, что «преждевременная оптимизация является корнем всего зла», нет никаких доказательств того, что это даст вам какие-либо преимущества, и в результате ваш код станет значительно более сложным.

Насколько я знаю, вы не можете изменить контекст выполнения для JavaFX / ScalaFX , поскольку создание JAT должно точно контролироваться в Чтобы гарантировать безопасность ниток. Но я могу ошибаться.

В любом случае накладные расходы, связанные с наличием разных диспетчеров, не будут высокими. Одна из причин использования фьючерсов и актеров заключается в том, что они позаботятся об этих проблемах по умолчанию. Если у вас нет веских причин поступить иначе, я бы использовал значения по умолчанию.

...