Play's execution contexts vs scala global

2019-01-13 13:22发布

How does the execution context from

import scala.concurrent.ExecutionContext.Implicits.global

differ from Play's execution contexts:

import play.core.Execution.Implicits.{internalContext, defaultContext}

2条回答
Bombasti
2楼-- · 2019-01-13 14:03

They are very different.

In Play 2.3.x and prior, play.core.Execution.Implicits.internalContext is a ForkJoinPool with fixed constraints on size, used internally by Play. You should never use it for your application code. From the docs:

Play Internal Thread Pool - This is used internally by Play. No application code should ever be executed by a thread in this thread pool, and no blocking should ever be done in this thread pool. Its size can be configured by setting internal-threadpool-size in application.conf, and it defaults to the number of available processors.

Instead, you would use play.api.libs.concurrent.Execution.Implicits.defaultContext, which uses an ActorSystem.

In 2.4.x, they both use the same ActorSystem. This means that Akka will distribute work among its own pool of threads, but in a way that is invisible to you (other than configuration). Several Akka actors can share the same thread.

scala.concurrent.ExecutionContext.Implicits.global is an ExecutionContext defined in the Scala standard library. It is a special ForkJoinPool that using the blocking method to handle potentially blocking code in order to spawn new threads in the pool. You really shouldn't use this in a Play application, as Play will have no control over it. It also has the potential to spawn a lot of threads and use a ton of memory, if you're not careful.

I've written more about scala.concurrent.ExecutionContext.Implicits.global in this answer.

查看更多
神经病院院长
3楼-- · 2019-01-13 14:15

They are the same and point out to the default dispatcher of the underlying actor system in your Play or Akka or combined application.

Default Play's Context

play.api.libs.concurrent.Execution.Implicits.defaultContext

Play's Internal Context

play.core.Execution.Implicits.internalContext

Guice's EC Injected

class ClassA @Inject()(config: Configuration)
                           (implicit ec: ExecutionContext) {
...
}

But this is different:

scala.concurrent.ExecutionContext.Implicits.global

Also DB drivers, e.g. if you use slick, may come up with their own Execution Context. Anyway,


Best Practices:

  • Don’t use scala.concurrent.ExecutionContext.Implicits.global, when you are in play or akka framework, in this way you may use more threads than optimum during high load so the performance may decrease.
  • Don’t afraid! use the default dispatcher as much as you want everywhere unless you do some blocking task for example listening on network connection, or reading from db explicitly that makes you “current threed” waiting for the result.
  • Start with default executor and if you found Play / Akka not responding well during high load, switch to a new thread pool for time consuming computation tasks.
    • Computational tasks that are taking long time is not usually considered blocking. For example traversing an auto completion tree in the memory. But you may considered them blocking when you want to have your control structures remaining functioning once you have a time taking computational task.
    • The bad thing that may happen when you consider computational tasks as non-blocking is that the play and Akka message dispatcher will be paused when all threads are computing in heavy load. The pros of a separate dispatcher is that the queue processor doesn’t starve. The Cons with separate dispatcher is that you may allocate more threads that optimum and your overall performance will be decreased.
    • The difference is for high load servers, don’t worry for small projects, use the default
  • Use scala.concurrent.ExecutionContext.Implicits.global when you have no other executor running in your app. Don’t worry this is safe then.
  • Once you create Futures, use the default pool, this is the safest way unless you are sure that the future is blocking. Then use a separate pool or use blocking{} structure if possible.
  • Create a separate thread pool once
    • You Await for a future
    • You call Thread.sleep
    • You are reading a stream/socket/http call
    • Manually querying db with a blocking driver, (usually slick is safe)
    • Schedule a task to be run in 10 second
    • Schedule a task to be run every second
  • For map/recover operations of a future, use the default executor, usually this is safe
  • Exception handling is safe with default dispatcher
  • Always use Akka dispatchers with you in Play or Akka, has a nice way to define a new dispatcher in application.conf
查看更多
登录 后发表回答