Как получить исключения, брошенные в Scala Будущее?

Я работал над своим ответом на Существует ли стандартная функция Scala для запуска блока с тайм-аутом? и возникла проблема, если исключение выбрано в будущем.

  def runWithTimeout[T](timeoutMs: Long)(f: => T) : Option[T] = {
    awaitAll(timeoutMs, future(f)).head.asInstanceOf[Option[T]]
  }

Итак,

runWithTimeout(50) { "result" } should equal (Some("result"))
runWithTimeout(50) { Thread.sleep(100); "result" } should equal (None)

Но если я создаю исключение в своем блоке, он не просачивается, а проглатывается - так что следующее происходит с ошибкой ".. исключение исключено"

intercept[Exception] {
    runWithTimeout(50) { throw new Exception("deliberate") }
}.getMessage should equal("deliberate")

Syserr имеет трассировку стека с сообщением

<function0>: caught java.lang.Exception: deliberate

но я не могу найти, где в Scala время выполнения, которое печатается.

Помимо обертывания f в другом блоке, который ловит исключения и распространяет их при броске, есть ли способ убедить awaitAll и/или Future бросить?

Ответы

Ответ 1

Короткий ответ: нет.

Исключения не делают то, что вы хотите, когда работаете в потоковом контексте, потому что хотите узнать об исключении в вызывающем, и исключение происходит в будущем потоке.

Вместо этого, если вы хотите знать, что такое исключение, вы должны вернуть Either[Exception,WhatYouWant] - конечно, вы должны поймать это исключение в будущем и упаковать его.

scala> scala.actors.Futures.future{
  try { Right("fail".toInt) } catch { case e: Exception => Left(e) }
}
res0: scala.actors.Future[Product with Serializable with Either[Exception,Int]] = <function0>

scala> res0()   // Apply the future
res1: Product with Serializable with Either[Exception,Int] =
      Left(java.lang.NumberFormatException: For input string: "fail")

Ответ 2

Отказ от ответственности: я работаю для Typesafe

Или... вы можете использовать Akka, и это даст вам то, что вы хотите, без необходимости проходить через обручи.

val f: Future[Int] = actor !!! message

Тогда

    f.get 

Выбросит исключение, которое произошло в акторе

    f.await.exception 

предоставит вам опцию [Throwable]

Ответ 3

Работая с моим предложением @Rex Kerr, я создал

object Timeout {

  val timeoutException = new TimeoutException

  def runWithTimeout[T](timeoutMs: Long)(f: => T) : Either[Throwable, T] = {
    runWithTimeoutIgnoreExceptions(timeoutMs)(exceptionOrResult(f)) match {
      case Some(x) => x
      case None => Left(timeoutException)
    }
  }

  def runWithTimeout[T](timeoutMs: Long, default: T)(f: => T) : Either[Throwable, T] = {
    val defaultAsEither: Either[Throwable, T] = Right(default)
    runWithTimeoutIgnoreExceptions(timeoutMs, defaultAsEither)(exceptionOrResult(f))
  }

  def runWithTimeoutIgnoreExceptions[T](timeoutMs: Long)(f: => T) : Option[T] = {
    awaitAll(timeoutMs, future(f)).head.asInstanceOf[Option[T]]
  }

  def runWithTimeoutIgnoreExceptions[T](timeoutMs: Long, default: T)(f: => T) : T = {
    runWithTimeoutIgnoreExceptions(timeoutMs)(f).getOrElse(default)
  }

  private def exceptionOrResult[T](f: => T): Either[Throwable, T] = 
    try { 
      Right(f) 
    } catch { 
      case x => Left(x)
    }
}

так что

  @Test def test_exception {
    runWithTimeout(50) { "result" }.right.get should be ("result")
    runWithTimeout(50) { throw new Exception("deliberate") }.left.get.getMessage should be ("deliberate")
    runWithTimeout(50) { Thread.sleep(100); "result" }.left.get should be (Timeout.timeoutException)

    runWithTimeout(50, "no result") { "result" }.right.get should be ("result")
    runWithTimeout(50, "no result") { throw new Exception("deliberate") }.left.get.getMessage should be ("deliberate")
    runWithTimeout(50, "no result") { Thread.sleep(100); "result" }.right.get should be ("no result")

}

Опять же, я немного новичок Scala, так что бы приветствовать обратную связь.

Ответ 4

scala.concurrent.ops.future включает обработку исключений.

Итак, вместо импорта scala.actors.Futures.future, импортируйте scala.concurrent.ops.future instead.

Это простое изменение, в котором происходит импорт, вызовет вызов вызывающего абонента .get, чтобы восстановить исключение. Он отлично работает!

Ответ 5

Или используйте Future.liftTryTry, превращая его из Future[Object] в Future[Try[Object]], и вы можете сопоставлять его с Try[Object] и проверять исключение case Throw(e) и изящно лог/выйти

Ответ 6

Для исключения исключений вам необходимо переопределить метод exceptionHandler. Таким образом, ваш вариант состоит в том, чтобы определить ваш собственный метод future, чтобы он создавал MyFutureActor с исключениемHandler.

EDIT: FutureActor является приватным, поэтому подклассификация невозможна.

Другой вариант - использовать ссылки, чтобы знать, когда произошли исключения.

Однако я думаю, что подход Рекса Керра лучше - просто оберните функцию в нечто, что поймает Исключение. Слишком плохо future этого не делает.