我一直在处理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抛出?
答案 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
尚未做到这一点。