我有一个简单的cats-effect应用程序,该应用程序从作为参数提供的URL下载站点。在下载过程中,应通过在控制台上写点(.
)来显示“加载栏”。我通过对两个 IOs 进行 race 来实现,其中一个用于下载另一个用于显示点的IO。
这是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。
唯一的问题是应用程序永远不会退出。
据我检查 loader IO 是否正确取消。我什至可以添加如下内容:
urlLoader.run(args) *> console.putStrLn("???") *> IO(ExitCode.Success)
然后显示???
。
当我删除 race 时,应用也会正确退出。
所以我的问题是如何解决这个问题并使应用最终退出?
答案 0 :(得分:3)
要继续我的上述评论:问题是您的ScheduledExecutorService
具有正在运行的线程,这些线程阻止JVM退出,即使您的计时器任务已被取消。有几种方法可以解决此问题:
IO(ses.shutdown())
之前添加IO(ExitCode.Success)
。newScheduledThreadPool
。timer: Timer
内部使用免费获得的IOApp
。最后一个几乎绝对是正确的选择-使用ContextShift
提供的计时器(和IOApp
)将为您提供针对此行为和其他行为的合理默认值。