Как мне получить исключения, выданные в Scala Future? - PullRequest
15 голосов
/ 03 июня 2011

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

  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 бросить?

Ответы [ 6 ]

14 голосов
/ 03 июня 2011

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

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

Вместо этого, если вы хотите узнать, что было исключением, вы должны вернуть 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")
10 голосов
/ 05 июня 2011

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

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

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

Тогда

    f.get 

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

    f.await.exception 

даст вам опцию [Бросок]

2 голосов
/ 16 ноября 2011

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

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

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

2 голосов
/ 04 июня 2011

Проходя путь по предложению @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, поэтомудобро пожаловать отзыв.

0 голосов
/ 14 декабря 2017

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

0 голосов
/ 03 июня 2011

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

РЕДАКТИРОВАТЬ: FutureActor является частным, поэтому создание подклассов это невозможно.

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

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

...