§Scheduling asynchronous tasks
You can schedule sending messages to actors and executing tasks (functions or Runnable
instances). You will get a Cancellable
back that you can call cancel
on to cancel the execution of the scheduled operation.
For example, to send a message to the testActor
every 30 seconds:
- Scala
-
/* * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com> */ package tasks import javax.inject.Inject import javax.inject.Named import akka.actor.ActorRef import akka.actor.ActorSystem import scala.concurrent.ExecutionContext import scala.concurrent.duration._ class MyActorTask @Inject()(actorSystem: ActorSystem, @Named("some-actor") someActor: ActorRef)( implicit executionContext: ExecutionContext ) { actorSystem.scheduler.schedule( initialDelay = 0.microseconds, interval = 30.seconds, receiver = someActor, message = "tick" ) }
- Java
-
/* * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com> */ package tasks; import javax.inject.Named; import javax.inject.Inject; import akka.actor.ActorRef; import akka.actor.ActorSystem; import scala.concurrent.ExecutionContext; import scala.concurrent.duration.Duration; import java.util.concurrent.TimeUnit; public class MyActorTask { private final ActorRef someActor; private final ActorSystem actorSystem; private final ExecutionContext executionContext; @Inject public MyActorTask( @Named("some-actor") ActorRef someActor, ActorSystem actorSystem, ExecutionContext executionContext) { this.someActor = someActor; this.actorSystem = actorSystem; this.executionContext = executionContext; this.initialize(); } private void initialize() { actorSystem .scheduler() .schedule( Duration.create(0, TimeUnit.SECONDS), // initialDelay Duration.create(30, TimeUnit.SECONDS), // interval someActor, "tick", // message, executionContext, ActorRef.noSender()); } }
Note: See Scala or Java documentation about how to inject actors.
Similarly, to run a block of code 10 seconds from now, every minute:
- Scala
-
class CodeBlockTask @Inject()(actorSystem: ActorSystem)(implicit executionContext: ExecutionContext) { actorSystem.scheduler.schedule(initialDelay = 10.seconds, interval = 1.minute) { // the block of code that will be executed print("Executing something...") } }
- Java
-
public class CodeBlockTask { private final ActorSystem actorSystem; private final ExecutionContext executionContext; @Inject public CodeBlockTask(ActorSystem actorSystem, ExecutionContext executionContext) { this.actorSystem = actorSystem; this.executionContext = executionContext; this.initialize(); } private void initialize() { this.actorSystem .scheduler() .schedule( Duration.create(10, TimeUnit.SECONDS), // initialDelay Duration.create(1, TimeUnit.MINUTES), // interval () -> System.out.println("Running block of code"), this.executionContext); } }
Or to run a block of code once 10 seconds from now:
- Scala
-
class ScheduleOnceTask @Inject()(actorSystem: ActorSystem)(implicit executionContext: ExecutionContext) { actorSystem.scheduler.scheduleOnce(delay = 10.seconds) { // the block of code that will be executed print("Executing something...") } }
- Java
-
/* * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com> */ public class CodeBlockOnceTask { private final ActorSystem actorSystem; private final ExecutionContext executionContext; @Inject public CodeBlockOnceTask(ActorSystem actorSystem, ExecutionContext executionContext) { this.actorSystem = actorSystem; this.executionContext = executionContext; this.initialize(); } private void initialize() { this.actorSystem .scheduler() .scheduleOnce( Duration.create(10, TimeUnit.SECONDS), // delay () -> System.out.println("Running just once."), this.executionContext); } }
You can see the Akka documentation to see other possible uses of the scheduler. See the documentation for akka.actor.Scheduler
for Scala or for Java.
Note: Instead of using the default
ExecutionContext
, you can instead create aCustomExecutionContext
. See documentation for Java or Scala. See the section about it below.
§Starting tasks when your app starts
After defining the tasks as described above, you need to initialize them when your application starts.
§Using Guice Dependency Injection
When using Guice Dependency Injection, you will need to create and enable a module to load the tasks as eager singletons:
- Scala
-
/* * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com> */ package tasks import play.api.inject.SimpleModule import play.api.inject._ class TasksModule extends SimpleModule(bind[MyActorTask].toSelf.eagerly())
- Java
-
/* * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com> */ package tasks; import com.google.inject.AbstractModule; public class TasksModule extends AbstractModule { @Override protected void configure() { bind(MyActorTask.class).asEagerSingleton(); } }
And then enable the module in your application.conf
by adding the following line:
play.modules.enabled += "tasks.TasksModule"
As the task definitions are completely integrated with the Dependency Injection framework, you can also inject any necessary component inside of them. For more details about how to use Guice Dependency Injection, see Scala or Java documentation.
§Using compile-time Dependency Injection
When using compile-time Dependency Injection, you just need to start them in your implementation of BuiltInComponents
:
- Scala
-
/* * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com> */ package tasks import play.api.ApplicationLoader.Context import play.api.routing.Router import play.api.BuiltInComponentsFromContext import play.api.NoHttpFiltersComponents class MyBuiltInComponentsFromContext(context: Context) extends BuiltInComponentsFromContext(context) with NoHttpFiltersComponents { override def router: Router = Router.empty // Task is initialize here initialize() private def initialize(): Unit = { new CodeBlockTask(actorSystem) } }
- Java
-
/* * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com> */ package tasks; import play.ApplicationLoader; import play.BuiltInComponentsFromContext; import play.filters.components.NoHttpFiltersComponents; import play.routing.Router; public class MyBuiltInComponentsFromContext extends BuiltInComponentsFromContext implements NoHttpFiltersComponents { public MyBuiltInComponentsFromContext(ApplicationLoader.Context context) { super(context); this.initialize(); } private void initialize() { // Task is initialize here new CodeBlockTask(actorSystem(), executionContext()); } @Override public Router router() { return Router.empty(); } }
This must then be used with your custom ApplicationLoader
implementation. For more details about how to use compile-time Dependency Injection, see Scala or Java documentation.
§Using a CustomExecutionContext
You should use a custom execution context when creating tasks that do sync/blocking work. For example, if your task is accessing a database using JDBC, it is doing blocking I/O. If you use the default execution context, your tasks will then block threads that are using to receive and handle requests. To avoid that, you should provide a custom execution context:
- Scala
-
import javax.inject.Inject import akka.actor.ActorSystem import play.api.libs.concurrent.CustomExecutionContext class TasksCustomExecutionContext @Inject()(actorSystem: ActorSystem) extends CustomExecutionContext(actorSystem, "tasks-dispatcher")
- Java
-
import akka.actor.ActorSystem; import play.libs.concurrent.CustomExecutionContext; import scala.concurrent.duration.Duration; import javax.inject.Inject; import java.util.concurrent.TimeUnit; public class TasksCustomExecutionContext extends CustomExecutionContext { @Inject public TasksCustomExecutionContext(ActorSystem actorSystem) { super(actorSystem, "tasks-dispatcher"); } }
Configure the thread pool as described in thread pools documentation using tasks-dispatcher
as the thread pool name, and then inject it in your tasks:
- Scala
-
class SomeTask @Inject()(actorSystem: ActorSystem, executor: TasksCustomExecutionContext) { actorSystem.scheduler.schedule(initialDelay = 10.seconds, interval = 1.minute)({ print("Executing something...") })(executor) // using the custom execution context }
- Java
-
public class SomeTask private final ActorSystem actorSystem; private final TasksCustomExecutionContext executor; @Inject public SomeTask(ActorSystem actorSystem, TasksCustomExecutionContext executor) { this.actorSystem = actorSystem; this.executor = executor; this.initialize(); } private void initialize() { this.actorSystem .scheduler() .schedule( Duration.create(10, TimeUnit.SECONDS), // initialDelay Duration.create(1, TimeUnit.MINUTES), // interval () -> System.out.println("Running block of code"), this.executor // using the custom executor ); } }
§Use third party modules
There are also modules that you can use to schedule tasks. Visit our module directory page to see a list of available modules.
Next: Contributing to Play