2016-12-20 2 views
1

J'ai un programme scala qui s'exécute pendant un certain temps puis se termine. Je voudrais fournir une bibliothèque à ce programme qui, dans les coulisses, programme une tâche asynchrone pour exécuter chaque N secondes. Je voudrais aussi que le programme se termine lorsque le travail de main entrypoint est terminé sans avoir besoin de dire explicitement au travail d'arrière-plan de s'arrêter (puisqu'il se trouve dans une bibliothèque). Comme le mieux que je peux dire la façon idiomatique d'effectuer un sondage ou un travail programmé dans Scala est avec ActorSystem.scheduler.schedule d'Akka, mais en utilisant un ActorSystem, le programme se bloque après main en attendant les acteurs. J'ai ensuite essayé et échoué à ajouter un autre acteur que join s sur le fil principal, apparemment parce que "Anything that blocks a thread is not advised within Akka"Planification idiomatique du travail en arrière-plan qui disparaît avec le thread principal dans Scala

Je pourrais introduire un répartiteur personnalisé; Je pourrais mélanger quelque chose ensemble avec un sondage isAlive, ou en ajoutant un contrôle similaire à l'intérieur de chaque travailleur; ou je pourrais abandonner sur Akka et juste utiliser des Threads bruts. Cela ressemble à une chose pas trop inhabituelle à vouloir faire, donc je voudrais utiliser Scala idiomatique s'il y a une meilleure façon claire.

+0

Vous pouvez simplement bloquer le thread principal en utilisant 'Await.result (whenTerminated, Duration.Inf)' et déclencher la terminaison du système de l'acteur si nécessaire. –

Répondre

1

Je ne pense pas qu'il y ait une manière idiomatique de Scala.

Le programme JVM se termine lorsque tous les threads non-daemon sont terminés. Vous pouvez donc planifier votre tâche pour qu'elle s'exécute sur un thread démon.

Donc il suffit d'utiliser la fonctionnalité 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) 
    } 
} 

Vous pouvez également utiliser guava pour créer une usine de thread démon avec new ThreadFactoryBuilder().setDaemon(true).build().

1

Si vous utilisez le planificateur Akka, vous bénéficierez d'une mise en œuvre optimisée et très bien testée. Élever un système d'acteur est un peu lourd, je suis d'accord. De plus, vous devez apporter une dépendance à Akka. Si vous êtes d'accord avec cela, vous pouvez appeler explicitement system.shutdown à partir de main lorsque vous avez terminé, ou envelopper dans une fonction qui le fera pour vous.

Sinon, vous pouvez essayer quelque chose le long de ces lignes:

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") 
} 

Prints:

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 

Je n'aime pas qu'il utilise Thread.sleep, mais cela est fait pour éviter d'utiliser tout autre 3 les programmateurs de fête et Scala Future ne fournissent pas d'options de délai d'attente. Donc, vous allez gaspiller un thread sur cette tâche de planification, mais c'est ce que le planificateur Akka semble to do anyway. La différence est que peut-être vous voulez un seul planificateur pour l'ensemble de la JVM ne pas perdre trop de threads. Le code que j'ai fourni, quoique plus simple, gaspillera un fil par tâche.