2011-06-03 48 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)和登錄/退出優雅