2011-06-03 92 views
14

我一直在处理我对Is there a standard Scala function for running a block with a timeout?的回答,并且如果在Future中引发异常,则会遇到问题。如何获取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) 

但是,如果我可以把我的块中的例外,但不得泄漏,但吞噬 - 这样下失败,“..no抛出异常”

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

SYSERR具有与消息

<function0>: caught java.lang.Exception: deliberate 
栈跟踪

但我无法找到打印的Scala运行时的哪个位置。

除了将f封装在另一个捕获异常的块中并在抛出异常时传播它们,有没有什么办法可以说服awaitAll和/或Future抛出?

+0

它可能是打印,因为它被认为是传递给线程的[UncaughtExceptionHandler的] (http://download.oracle.com/javase/6/docs/api/java/lang/Thread.UncaughtExceptionHandler.html)。你可以设置你自己的处理程序,但是这仍然不允许你在另一个线程中抛出异常。 – 2011-06-03 16:36:12

+1

看看Fingales期货(https://github.com/twitter/finagle),搜索“Timeout”和Akka http://akka.io/docs/akka/1.1.2/scala/futures.html – oluies 2011-06-03 17:48:07

回答

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") 
+0

Java的'Future.get()'抛出'ExecutionException',它在执行代码中包装任何异常。这是我的模型。 – 2011-06-03 19:23:22

+0

@Duncan McGregor - 我不知道Java是如何完成它的,但如果它是作为一个库来完成的,没有多少选择,只能让线程采取异常,打包并在另一端处理它。我的猜测是Java为你做了它,因为它没有任何通用的机制来允许这样做;你需要自己动手Scala(使用为此提供的一般机制)。 – 2011-06-03 19:33:41

+0

谢谢 - 看起来像我会学习的另一个成语! – 2011-06-03 20:44:42

-1

您需要重写方法exceptionHandler以捕获异常。所以你的选择是定义你自己的future方法,所以它使用exceptionHandler创建一个MyFutureActor。

编辑:FutureActor是私人的,所以子类化它是不可能的。

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

但是,我认为雷克斯克尔的方法更好 - 只需将函数包装到可以捕获异常的东西中即可。太糟糕了future尚未做到这一点。

+0

我喜欢这个的声音,但是FutureActor是私人的和复杂的,足以复制它会很毛茸茸的。 – 2011-06-03 20:58:22

2

通过@Rex克尔的建议工作我的方式,我创建

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

10

声明:我类型安全

工作

或者....你可以使用Akka,它会给你想要的东西没有你不必去赴汤蹈火吧。

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

然后

f.get 

将抛出,在发生了演员除了

f.await.exception 

会给你一个选项[Throwable的]

+0

很高兴知道我并不那么认为它应该是可能的。 – 2011-06-05 07:47:01

2

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

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

在那里导入的简单改变将导致调用者对.get的调用重新抛出异常。它很棒!

+0

似乎在Scala 2.11中删除了'scala.concurrent.ops.future',因此此答案不再适用:https://github.com/scala/scala/commit/67d7e26657a0a52e2bd5dc46bd1bbedda52d2dc0#L4L41 – 2017-05-26 11:12:24

0

或者使用Future.liftTryTry,原来从Future[Object]Future[Try[Object]],并且可以匹配的Try[Object]并检查异常case Throw(e)和登录/退出优雅