Использование гонки от кошки-эффект предотвращает выход приложения - PullRequest
4 голосов
/ 21 июня 2019

У меня есть простое приложение с эффектом кошки, которое скачивает сайт с URL, указанного в качестве аргумента.Во время загрузки приложение должно отображать «полосу загрузки», записывая точки (.) в консоль.Я реализовал это, выполнив гонку из двух операций ввода-вывода одного для загрузки другого для отображения точек.

Это целое приложение на scastie .

Самая важная часть здесь:

def loader(): IO[Unit] = for {
      _ <- console.putStr(".")
      _ <- timer.sleep(Duration(50, MILLISECONDS)) *> loader()
    } yield {}


  def download(url: String): IO[String] = IO.delay(Source.fromURL(url)).map(_.mkString)

  def run(args: List[String]): IO[Unit] = {

    args.headOption match {
      case Some(url) =>
        for {
          content <- IO.race(download(url), loader()).map(_.left.get)
          _ <- console.putStrLn() *> console.putStrLn(s"Downloaded site from $url. Size of downloaded content is ${content.length}.")
        } yield {}

      case None => console.putStrLn("Pass url as argument.")
    }
  }

Все работает, как я ожидал, когда я запускаю его, я получаю:

.............. Скачанный сайт с https://www.scala -lang.org .Размер загружаемого контента 47738.

Единственная проблема в том, что приложение никогда не закрывается.

Насколько я проверял загрузчик IO корректно отменяется.Я даже могу добавить что-то вроде этого:

urlLoader.run(args) *> console.putStrLn("???") *> IO(ExitCode.Success)

И отображается ???.

Также, когда я удаляю race , приложение корректно завершает работу.

Итак, мой вопрос, как я могу это исправить и завершить работу моего приложения?

1 Ответ

3 голосов
/ 23 июня 2019

Чтобы прокомментировать мой комментарий выше: проблема в том, что на вашем ScheduledExecutorService запущены потоки, которые не позволяют завершить работу JVM, даже если задачи вашего таймера были отменены. Есть несколько способов решить эту проблему:

  • Добавить IO(ses.shutdown()) перед IO(ExitCode.Success).
  • Вызовите newScheduledThreadPool с фабрикой потоков, которая демонизирует свои потоки.
  • Используйте timer: Timer, который вы получаете бесплатно внутри IOApp.

Последний из них почти наверняка является правильным выбором - использование таймера (и ContextShift), предоставленного IOApp, даст вам разумные значения по умолчанию для этого и других вариантов поведения.

...