How can I run an Async IO operation that doesn't block the main thread but throws an exception in the main thread if it fails?

Viewed 214

I'm investigating Cats to accomplish the above. I tried writing the below example to

  1. Run an IO operation that doesn't block the main thread
  2. Throws an exception in the main thread if it fails
import cats.effect.{IO, Async}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

val apiCall = Future.successful("I come from the Future!")

val ioa: IO[String] =
  Async[IO].async { cb =>
    import scala.util.{Failure, Success}
    Thread.sleep(1000)
    throw new RuntimeException

    apiCall.onComplete {
      case Success(value) => cb(Right(value))
      case Failure(error) => cb(Left(error))
    }
 }

ioa.unsafeRunAsync(result => result match {
    case Left(result) => throw result
    case Right(_) =>
})

However it seems to fail at both of those things. It blocks the main thread and only throws an exception in the child thread. Is what I'm trying to do possible?

1 Answers

You can do something like this:

val longRunningOperation: IO[Unit] = ...
val app: IO[Unit] = ...

val program: IO[Unit] =
  IO.racePair(app, longRunningOperation).flatMap {
    case Left((_, fiberLongRunning)) =>  fiberLongRunning.cancel.void
    case Right((fiberApp, _)) => fiberApp.join.void
  }

override def run(args: List[String]): IO[ExitCode] =
  program.as(ExitCode.Success)

racePair will run two IOs concurrently and in case one of the two fails, then it will cancel the other one.
Now, if the longRunningOperation finishes without error, then er wait for the app; But, if the app is the one that finished first, then I decided to cancel the longRunningOperation (you can, of course, change that).


Thanks to Adam Rosien for pointing out that if you want to join in both cases then best would be:

val program: IO[Unit] = (app, longRunningOperation).parTupled.void

You can see the code running and play with it here

Related