Как подождать завершения обратного вызова в режиме Scala forSuccess?

В Scala я могу использовать Await, чтобы дождаться, когда будет завершено будущее. Однако, если я зарегистрировал обратный вызов для запуска по завершении этого будущего, как я могу ждать не только для будущего завершения, но и для завершения этого обратного вызова?

Вот минимальная, но полная программа, иллюстрирующая проблему:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, Future }

object Main {
  def main(args: Array[String]): Unit = {
    val f: Future[Int] = Future(0)
    f.onSuccess { case _ =>
      Thread.sleep(10000)
      println("The program waited patiently for this callback to finish.")
    }

    // This waits for `f` to complete but doesn't wait for the callback
    // to finish running.
    Await.ready(f, Duration.Inf)
  }
}

Я ожидаю, что вывод будет:

The program waited patiently for this callback to finish.

Вместо этого нет выхода; программа завершает работу до завершения обратного вызова.

Обратите внимание, что это не та же проблема, что и в ожидании будущего завершения, на который ранее был дан ответ на этот вопрос.

Ответ 1

Не используйте обратный вызов onSuccess, но вместо этого выполняйте побочный эффект в вызове Future.map. Таким образом, у вас есть Future [Unit], чтобы использовать Await on.

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, Future }

object Main {
  def main(args: Array[String]): Unit = {
    val f: Future[Int] = Future(0)
    val f2: Future[Unit] = f.map { x =>
      Thread.sleep(10000)
      println("The program waited patiently for this callback to finish.")
    }

    Await.ready(f2, Duration.Inf)
  }
}

Обратите внимание, что если вы хотите выполнить побочный эффект только в случае успеха (например, в вашем примере), карта подходит. Если вы хотите выполнить побочный эффект также в случае сбоя, а затем - правильный метод. Смотрите сообщение от Roland Kuhn на scala -user.

Кроме того, не используйте Thread.sleep в любом месте рядом с производственным кодом.

Ответ 2

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, Future }
import scala.util._

object Main {
  def main(args: Array[String]): Unit = {
    val f1: Future[Int] = Future(0)
    val f2 = f1 andThen {
      case Success(v) =>
        Thread.sleep(10000)
        println("The program waited patiently for this callback to finish.")
      case Failure(e) =>
        println(e)
    }

    Await.ready(f1, Duration.Inf)
    println("F1 is COMPLETED")
    Await.ready(f2, Duration.Inf)
    println("F2 is COMPLETED")
  }
}

печатает:

F1 is COMPLETED
The program waited patiently for this callback to finish.
F2 is COMPLETED

Использование promises еще более понятно:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent._
import scala.util._

object Main {
  def main(args: Array[String]): Unit = {
    val f: Future[Int] = Future(0)
    val p = Promise[Unit]()
    p.future.onSuccess { case _ =>
      println("The program waited patiently for this callback to finish.")
    }
    f.onSuccess { case _ =>
      Thread.sleep(10000)
      p.success(())
    }

    Await.ready(f, Duration.Inf)
    println("F is COMPLETED")
    Await.ready(p.future, Duration.Inf)
    println("P is COMPLETED")
  }
}

печатает:

F is COMPLETED
P is COMPLETED
The program waited patiently for this callback to finish.