ScalaFX и эффект кошек

Я пытаюсь интегрировать Cats Effect в настольное приложение ScalaFX, и у меня возникают проблемы с выполнением задач. Я хотел бы запустить фоновый поток/волокно для инициализации окна при его отображении. Я ДУМАЮ, что делаю следующее:

  1. Запуск приложения JavaFX на блокирующем вводе-выводе.
  2. JavaFX делает кучу вещей, включая запуск собственных потоков и прочее. Я не уверен, имеет ли это значение.
  3. В потоке «JavaFX Application Thread» выполняется инициализируемый метод контроллера (подтвержденный println) и создается ввод-вывод, который я хотел бы выполнить.
  4. Я создаю Supervisor для его выполнения в фоновом режиме.
  5. Он не печатает «Привет».

Я попробовал несколько решений, например вызов SupervisorIO.evalOn() вместо простого вызова ниже. Я попытался вызвать «join» в потоке, созданном Supervisor#supervise, чтобы попытаться принудительно выполнить его, но ничего не происходит.

Я упростил приложение, чтобы оно выглядело следующим образом (FXML — это просто BorderPane и Label, и он настраивает класс контроллера; я не думал, что вам это понадобится):

object CatsTest extends IOApp.Simple {
  override def run: IO[Unit] = {
    IO.blocking {
      Application.launch(classOf[CatsTestMain])
    }
  }
}

class CatsTestMain extends Application {
  override def start(primaryStage: Stage): Unit = {
    val screen = getClass.getResource("/main-screen.fxml")
    val loader = new FXMLLoader()
    loader.setLocation(screen)
    val root: Parent = loader.load[javafx.scene.Parent]
    val controller = loader.getController[MainController]()

    primaryStage.setScene(new Scene(new javafx.scene.Scene(root)))
    primaryStage.show()
  }
}

class MainController extends Initializable {
  override def initializable(location: URL, resources: ju.ResourceBundle): Unit = {
    println("in controller")
    val supervisor = Supervisor[IO](await=true)
    supervisor.use { supervisor =>
      supervisor.supervise(IO.println("Hello"))
    }
  }
}

Я не знаю, нужно ли мне возвращаться к исходному потоку, чтобы использовать ресурсы Cats, или в чем проблема. Я новичок в Cats Effect, и это было первое написанное мною за последнее время приложение, которое не обязательно использовало Akka/Pekko, так что это казалось хорошим способом изучить его, но я не могу не думаю, что я создал больше проблем, чем решил, не просто используя объекты Future.

ОТВЕЧАТЬ: Окончательный код, который печатает «В контроллере», а затем «Привет», приведен ниже. В CatsTest.run, создав ресурс диспетчера и затем вызвав «use», он создал IO для передачи IOApp, который он мог запустить. Этот ввод-вывод создает фабричный метод, который имеет доступ к диспетчеру, а затем создает блокирующий ввод-вывод, из которого можно запустить приложение JavaFX, определенное в CatsTestMain. (Я полагаю, что CATS-способ сделать это — поместить первую часть в один ввод-вывод и связать его с блокирующим вводом-выводом). Затем приложение JavaFX использует фабричный метод контроллера, определенный выше в FXMLLoader, для передачи диспетчера классу Controller, чтобы его мог использовать оконный контроллер. После этого JavaFX вызывает Initializable#initialize на контроллере и выводит «В контроллере», после чего диспетчер можно использовать для выполнения ввода-вывода.

object CatsTest extends IOApp.Simple {
  var factory: javafx.util.Callback[Class[?], Object] = null

  override def run: IO[Unit] = {
    Dispatcher.sequential[IO] use { dispatcher =>
      factory = { (tpe: Class[?]) => 
        if (classOf[MainController].isAssignableFrom(tpe)) {
          tpe.newInstance().asInstanceOf[MainController].dispatcherOpt = Some(dispatcher)
        } else {
          tpe.newInstance()
        }
      }
      IO.blocking { Application.launch(classOf[CatsTestMain]) }
    }
  }
}

class CatsTestMain extends Application {
  override def start(primaryStage: Stage): Unit = {
    ... the stuff above to initialize the loader
    loader.setControllerFactory(CatsTest.factory)
    ... the rest of the stuff to create and show the stage
  }
}

class MainController extends Intializable {
  var dispatcherOpt: Option[Dispatcher[IO]] = None

  override def intialize(location: URL, resources: ju.ResourceBundle): Unit = {
    println("In controller")
    dispatcherOpt.map(dispatcher => {
      dispatcher.unsafeRunAndForget(IO.println("Hello"))
    })
  }

В интерфейсе Initializable используется метод initialize() (не initializable()). Так что, скорее всего, ваш метод контроллера вообще не вызывается. Я не знаю, какую библиотеку вы используете (или, вообще, Scala), но похоже, что конструктор Supervisor — это блокирующий вызов. Вы не должны выполнять блокирующие вызовы в потоке приложения FX.

James_D 12.04.2024 18:23

Это кажется чрезвычайно запутанным даже для стандартов Java.

Luis Miguel Mejía Suárez 12.04.2024 21:18

@James_D, целью является перемещение блокирующих вызовов потока приложений FX. Это всего лишь одноцелевой инструмент, который я использую на работе, который я взломал, чтобы узнать что-то новое, поэтому этот проект является продолжением этого вопроса в Scala Users users.scala-lang.org/t/using-with -a-future/9887/6 . Наконец я нашел время попробовать Cats Effects вместо решения, основанного на будущем, которое я там использовал.

Jonathan Card 12.04.2024 21:24
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
87
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Здесь есть две проблемы:

Первый и самый важный из них заключается в том, что supervisor.use возвращает IO, который является просто описанием вычисления, не более того. Его необходимо явно запустить или упорядочить с другими IOs. Это фундаментальные знания, необходимые для написания приложений с эффектом кошек: IO это просто описания, значения, а не обработчики выполняемых вычислений; вопреки Future
Для такого типа взаимодействия вы хотите использовать Dispatcher, а не Supervisor: https://typelevel.org/cats-effect/api/3.x/cats/effect/std/Dispatcher.html, который дает вам нужно unsafeRunAndForget, чтобы отправить IO для работы в фоновом режиме.

Во-вторых, вы плохо управляете жизненным циклом. Вы не хотите создавать и уничтожать по одному Dispatcher на IO для выполнения. В идеале MainController должен получить уже выделенный Dispatcher, а что-то еще должно обеспечить его правильное закрытие, когда он больше не нужен.

В-третьих, если IOs, который вы планируете запустить, повлияет на интерфейс, его необходимо запускать в соответствующих потоках JavaFX.


так что это казалось хорошим способом выучить это

Лично я бы не согласился.

  • Во-первых, IO лучше подходит для веб-серверов, которым приходится иметь дело с параллелизмом.
  • Во-вторых, такие приложения, требующие двунаправленного взаимодействия, на самом деле труднее писать новичку. Потому что они ожидают, что вы будете владеть как внутренними компонентами JavaFX, так и моделью выполнения с эффектом кошки.

Было сказано, что. За последние годы многие люди построили подобные вещи. Вы можете обратиться за советом и ресурсами на серверы Discord.

Я попробовал пример здесь: typelevel.org/cats-effect/docs/std/dispatcher, но Dispatcher.sequential[IO] возвращает ресурс, а не Dispatcher, и этот диспетчер.use { .. } возвращает IO , так что это черепахи целиком. Я этого не понимаю. Я знаю, что ввод-вывод — это описание функции, но не могу заставить его работать. Я буду беспокоиться о жизненном цикле, когда он заработает. Я знаю, как передавать данные в поток JavaFX; Я не беспокоюсь об этом.

Jonathan Card 12.04.2024 20:29

@JonathanCard да, именно это я имею в виду, когда говорю «MainController должен получить уже выделенный Dispatcher». Попросите простой Dispatcher[IO] в конструкторе этого класса. В идеале тот, кто строит MainController, также должен попросить простой Dispatcher вплоть до main, где вы можете использовать Resource и предоставить подходящее IO для run. Однако, если это невозможно, вы можете использовать allocated на Resource, а затем использовать unsafeRunSync для запуска создания Dispatcher. - Вы также можете вообще отказаться от Dispatcher и использовать один из unsafeRun методов.

Luis Miguel Mejía Suárez 12.04.2024 20:42

@JonathanCard Обратите внимание, что вы можете передать параметр конструктору контроллера либо 1. опустив fx:controller = "..." в FXML, создав контроллер в коде и передав его fxmlLoader.setController(...) перед вызовом fxmlLoader.load(), либо 2. указав controllerFactory в FXMLLoader который определяет, как построить контроллер.

James_D 12.04.2024 20:59

Боже мой, я никогда не пойму, почему инструменты Java должны создавать простые вещи, такие как конструкторы и передача аргументов такого PITA. В любом случае, @JonathanCard, учитывая то, что сказал Джеймс, я бы сказал, что лучшее, что вы можете сделать, это просто использовать методы unsafeRun непосредственно на IO, который вы хотите запустить. Или создайте Dispatcher внутри конструктора или инициализируйте MainController, но кто знает, может ли это оказать негативное влияние на JavaFX.

Luis Miguel Mejía Suárez 12.04.2024 21:04

Я не видел здесь ваших комментариев, пока не проработал детали, а затем не отредактировал свой ответ, включив в него рабочий код. Спасибо за помощь.

Jonathan Card 12.04.2024 21:14

:) И у Scalafx есть несколько лучших шаблонов использования для работы с JavaFX, но они работают только со Scala 2.13, так что это еще один способ усложнить себе задачу.

Jonathan Card 12.04.2024 21:15

@JonathanCard ScalaFX поддерживает Scala 3: github.com/scalafx/scalafx/releases/tag/v.22.0.0-R33

Luis Miguel Mejía Suárez 12.04.2024 21:18

Ты прав; Я оговорился. Я имел в виду vigoo/ScalaFXML github.com/vigoo/scalafxml, который и занимается внедрением зависимостей. Последнее, что я видел, это не работало с макросами Scala 3.

Jonathan Card 12.04.2024 21:21

Другие вопросы по теме