如何掌握Scala Future中抛出的异常?

时间:2011-06-03 16:13:50

标签: scala exception-handling concurrency actor

我一直在处理Is there a standard Scala function for running a block with a timeout?的答案,并且如果在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 个答案:

答案 0 :(得分:14)

简答:不。

当您在线程上下文中工作时,异常不会执行您想要的操作,因为您想知道调用者中的异常,并且异常发生在将来的线程中。

相反,如果你想知道异常是什么,你应该返回一个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")

答案 1 :(得分:10)

免责声明:我为Typesafe工作

或....你可以使用Akka,它会给你你想要的东西,而不必为此完成箍。

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

然后

    f.get 

将抛出actor中发生的异常

    f.await.exception 

会给你一个选项[Throwable]

答案 2 :(得分:2)

通过@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新手,所以欢迎反馈。

答案 3 :(得分:2)

scala.concurrent.ops.future包含异常处理。

因此,不要导入scala.actors.Futures.future,而是导入scala.concurrent.ops.future instead

导入的简单更改将导致调用者调用.get来重新抛出异常。它很棒!

答案 4 :(得分:0)

或者使用Future.liftTryTry,将其从Future[Object]转为Future[Try[Object]],您可以匹配Try[Object]并检查例外case Throw(e)并记录/优雅地退出

答案 5 :(得分:-1)

您需要覆盖方法exceptionHandler以捕获异常。因此,您可以选择定义自己的future方法,以便使用exceptionHandler创建一个MyFutureActor。

编辑:FutureActor是私有的,因此无法进行子类化。

另一个选择是使用链接来了解异常发生的时间。

但是,我认为Rex Kerr的方法更好 - 只需将函数包装在可以捕获异常的内容中。太糟糕future尚未做到这一点。