EugeneMi
EugeneMi

Reputation: 3565

Avoid unnecessary context switching in scala futures

Let's say I have a code that does a blocking/long operation that returns a future, and then I need do a bunch of transformations on the results. A naive example is

longOperation().map(_ * 2).map(_.toString).map(_ + "bla").

Each of the maps introduces a context switch. Is there a simple way to avoid the context switches? I know about the trampoline execution context, and Scalaz tasks, but I am looking for something simpler that can be applied in very specific places where I know for a fact I don't need the context switch. (kinda similar to view on collections).

A more real example is I have a function that logs the execution time of a future - there is no reason to do a context switch just to record the time (not to mention that now I am measuring the execution time + the time it took the 'onComplete' to get picked up by the executor)

def timedFuture[T](metric: Histogram)(futureBlock: => Future[T])(implicit ec: ExecutionContext): Future[T] = {
  val startTime = System.nanoTime()
  val result: Future[T] = futureBlock
  result onComplete (_ => metric.record((System.nanoTime - startTime) / 1000000))
  result
}

Upvotes: 0

Views: 759

Answers (4)

Dima
Dima

Reputation: 40500

As long as you have more than one thread in the system, a context switch my happen at any time, it's up to the OS. While using .map somewhat increases the probability it'll happen at that exact moment, that doesn't really matter for anything, because the exact moment of the switch is insignificant (and outside of your control anyway). So, I wouldn't worry about that.

Upvotes: 0

jilen
jilen

Reputation: 5763

Actually there are some trampoline execution context in cats-effect or play framework. You can just copy such things to your project.

Note, don't do blocking call inside the flatMap/map body if you use such an execution context

Upvotes: 0

Viktor Klang
Viktor Klang

Reputation: 26579

If you have strict/synchronous transformations which you want to execute as a single thing, perform the transformation on the Try instance instead:

longOperation().transform(_.map(_ * 2).map(_.toString).map(_ + "bla"))

Source: https://viktorklang.com/blog/Futures-in-Scala-protips-5.html

Upvotes: 2

Zang MingJie
Zang MingJie

Reputation: 5275

Use a ExecutionContext that doesn't switch, like this one:

val currentThreadExecutionContext = ExecutionContext.fromExecutor(
  new Executor {
    def execute(runnable: Runnable) { runnable.run() }
})

From scala future document, there is also some arguments that suggesting not using this one, as the runnable (callback) maybe called in the unexpected thread. But you can use it if you fully understand how async future/promise works.

Upvotes: 0

Related Questions