Я пытаюсь интегрировать Cats Effect в настольное приложение ScalaFX, и у меня возникают проблемы с выполнением задач. Я хотел бы запустить фоновый поток/волокно для инициализации окна при его отображении. Я ДУМАЮ, что делаю следующее:
Я попробовал несколько решений, например вызов 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"))
})
}
Это кажется чрезвычайно запутанным даже для стандартов Java.
@James_D, целью является перемещение блокирующих вызовов потока приложений FX. Это всего лишь одноцелевой инструмент, который я использую на работе, который я взломал, чтобы узнать что-то новое, поэтому этот проект является продолжением этого вопроса в Scala Users users.scala-lang.org/t/using-with -a-future/9887/6 . Наконец я нашел время попробовать Cats Effects вместо решения, основанного на будущем, которое я там использовал.





Здесь есть две проблемы:
Первый и самый важный из них заключается в том, что 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 лучше подходит для веб-серверов, которым приходится иметь дело с параллелизмом.Было сказано, что. За последние годы многие люди построили подобные вещи. Вы можете обратиться за советом и ресурсами на серверы Discord.
Я попробовал пример здесь: typelevel.org/cats-effect/docs/std/dispatcher, но Dispatcher.sequential[IO] возвращает ресурс, а не Dispatcher, и этот диспетчер.use { .. } возвращает IO , так что это черепахи целиком. Я этого не понимаю. Я знаю, что ввод-вывод — это описание функции, но не могу заставить его работать. Я буду беспокоиться о жизненном цикле, когда он заработает. Я знаю, как передавать данные в поток JavaFX; Я не беспокоюсь об этом.
@JonathanCard да, именно это я имею в виду, когда говорю «MainController должен получить уже выделенный Dispatcher». Попросите простой Dispatcher[IO] в конструкторе этого класса. В идеале тот, кто строит MainController, также должен попросить простой Dispatcher вплоть до main, где вы можете использовать Resource и предоставить подходящее IO для run. Однако, если это невозможно, вы можете использовать allocated на Resource, а затем использовать unsafeRunSync для запуска создания Dispatcher. - Вы также можете вообще отказаться от Dispatcher и использовать один из unsafeRun методов.
@JonathanCard Обратите внимание, что вы можете передать параметр конструктору контроллера либо 1. опустив fx:controller = "..." в FXML, создав контроллер в коде и передав его fxmlLoader.setController(...) перед вызовом fxmlLoader.load(), либо 2. указав controllerFactory в FXMLLoader который определяет, как построить контроллер.
Боже мой, я никогда не пойму, почему инструменты Java должны создавать простые вещи, такие как конструкторы и передача аргументов такого PITA. В любом случае, @JonathanCard, учитывая то, что сказал Джеймс, я бы сказал, что лучшее, что вы можете сделать, это просто использовать методы unsafeRun непосредственно на IO, который вы хотите запустить. Или создайте Dispatcher внутри конструктора или инициализируйте MainController, но кто знает, может ли это оказать негативное влияние на JavaFX.
Я не видел здесь ваших комментариев, пока не проработал детали, а затем не отредактировал свой ответ, включив в него рабочий код. Спасибо за помощь.
:) И у Scalafx есть несколько лучших шаблонов использования для работы с JavaFX, но они работают только со Scala 2.13, так что это еще один способ усложнить себе задачу.
@JonathanCard ScalaFX поддерживает Scala 3: github.com/scalafx/scalafx/releases/tag/v.22.0.0-R33
Ты прав; Я оговорился. Я имел в виду vigoo/ScalaFXML github.com/vigoo/scalafxml, который и занимается внедрением зависимостей. Последнее, что я видел, это не работало с макросами Scala 3.
В интерфейсе
Initializableиспользуется методinitialize()(неinitializable()). Так что, скорее всего, ваш метод контроллера вообще не вызывается. Я не знаю, какую библиотеку вы используете (или, вообще, Scala), но похоже, что конструкторSupervisor— это блокирующий вызов. Вы не должны выполнять блокирующие вызовы в потоке приложения FX.