以惯用方式安排与Scala中的主线程一起消亡的后台工作

时间:2016-12-20 03:28:58

标签: scala akka actor

我有一个运行一段时间然后终止的scala程序。我想为这个程序提供一个库,在后台调度异步任务,每隔N秒运行一次。我还希望程序在main入口点的工作完成时终止,而不需要明确告诉后台工作关闭(因为它在库中)。

最好我可以告诉在Scala中进行轮询或预定工作的惯用方法是使用Akka的ActorSystem.scheduler.schedule,但使用ActorSystem会使程序在main等待演员后挂起。然后我尝试并且未能在主线程上添加另一个join的演员,似乎是因为"Anything that blocks a thread is not advised within Akka"

我可以介绍一个自定义调度员;我可以通过轮询isAlive检查,或在每个工人内部添加类似的支票来整理某些东西;或者我可以放弃Akka并使用原始线程。

这似乎是一个不太常见的事情,所以我想使用惯用的Scala,如果有一个明确的最佳方式。

2 个答案:

答案 0 :(得分:1)

我认为没有惯用的Scala方式。

当所有非守护程序线程完成时,JVM程序终止。因此,您可以安排任务在守护程序线程上运行。

所以只需使用Java功能:

import java.util.concurrent._

object Main {

  def main(args: Array[String]): Unit = {

    // Make a ThreadFactory that creates daemon threads.
    val threadFactory = new ThreadFactory() {
      def newThread(r: Runnable) = {
        val t = Executors.defaultThreadFactory().newThread(r)
        t.setDaemon(true)
        t
      }
    }

    // Create a scheduled pool using this thread factory
    val pool = Executors.newSingleThreadScheduledExecutor(threadFactory)

    // Schedule some function to run every second after an initial delay of 0 seconds
    // This assumes Scala 2.12. In 2.11 you'd have to create a `new Runnable` manually
    // Note that scheduling will stop, if there is an exception thrown from the function
    pool.scheduleAtFixedRate(() => println("run"), 0, 1, TimeUnit.SECONDS)

    Thread.sleep(5000)
  }
}

您还可以使用guava创建一个带new ThreadFactoryBuilder().setDaemon(true).build()的守护程序线程工厂。

答案 1 :(得分:1)

如果您使用Akka调度程序,您将依赖经过充分测试的高度优化和优化的实施。我同意,建立一个演员制度有点重。另外,你必须引入对akka的依赖。如果你没问题,你可以在完成时从main显式调用system.shutdown,或者将它包装在一个能为你完成的函数中。

或者,您可以尝试以下这些方面:

import scala.concurrent._
import ExecutionContext.Implicits.global

object Main extends App {

    def repeatEvery[T](timeoutMillis: Int)(f: => T): Future[T] = {
      val p = Promise[T]()
      val never = p.future
      f
      def timeout = Future {
        Thread.sleep(timeoutMillis)
        throw new TimeoutException
      }
      val failure = Future.firstCompletedOf(List(never, timeout))
      failure.recoverWith { case _ => repeatEvery(timeoutMillis)(f) }
    }

    repeatEvery(1000) {
      println("scheduled job called")
    }

    println("main started doing its work")
    Thread.sleep(10000)
    println("main finished")
}

打印:

scheduled job called
main started doing its work
scheduled job called
scheduled job called
scheduled job called
scheduled job called
scheduled job called
scheduled job called
scheduled job called
scheduled job called
scheduled job called
main finished

我不喜欢它使用Thread.sleep,但这样做是为了避免使用任何其他第三方调度程序,而Scala Future不提供超时选项。因此,您将在该调度任务上浪费一个线程,但这就是Akka调度程序似乎to do anyway。不同之处在于,您可能希望整个JVM的单个调度程序不会浪费太多线程。我提供的代码虽然比较简单,但每个作业都会浪费一个帖子。