2013-08-01 107 views
4

描述我的問題,一個簡單的代碼示例:等待「遞歸」期貨階

import scala.util._ 
import scala.concurrent._ 
import scala.concurrent.duration._ 
import ExecutionContext.Implicits.global 

class LoserException(msg: String, dice: Int) extends Exception(msg) { def diceRoll: Int = dice } 

def aPlayThatMayFail: Future[Int] = { 
    Thread.sleep(1000) //throwing a dice takes some time... 
    //throw a dice: 
    (1 + Random.nextInt(6)) match { 
     case 6 => Future.successful(6) //I win! 
     case i: Int => Future.failed(new LoserException("I did not get 6...", i)) 
    } 
} 

def win(prefix: String): String = { 
    val futureGameLog = aPlayThatMayFail 
    futureGameLog.onComplete(t => t match { 
     case Success(diceRoll) => "%s, and finally, I won! I rolled %d !!!".format(prefix, diceRoll) 
     case Failure(e) => e match { 
      case ex: LoserException => win("%s, and then i got %d".format(prefix, ex.diceRoll)) 
      case _: Throwable => "%s, and then somebody cheated!!!".format(prefix) 
     } 
    }) 
"I want to do something like futureGameLog.waitForRecursiveResult, using Await.result or something like that..." 
} 

win("I started playing the dice") 

這個簡單的例子說明了什麼我想做的事情。基本上,如果用文字說話,我想等待一些計算的結果,當我在先前的成功或不成功的attampts上撰寫不同的動作時。如何使用win方法?

我的「真實世界」的問題,如果這有什麼差別,使用dispatch異步HTTP調用,在這裏我想保留,只要前一個結束使HTTP調用,但操作不同,在羯羊以前的HTTP調用成功或不。

+1

你應該換你整個'aPlayThatMayFail'在未來'{...}'調用,而不是調用'Thread.sleep',然後一段時間後立即返回可用'未來' - 否則未來計算的阻塞部分將不會在未來運行,並將阻止調用者,就像任何非期貨代碼一樣。 –

+0

正確:)無論如何,它只是爲了說明這個想法... –

+0

是啊,我懷疑,但只是在以防萬一:) –

回答

5

您可以用遞歸調用恢復失敗的未來:

def foo(x: Int) = x match { 
    case 10 => Future.successful(x) 
    case _ => Future.failed[Int](new Exception) 
} 

def bar(x: Int): Future[Int] = { 
    foo(x) recoverWith { case _ => bar(x+1) } 
} 

scala> bar(0) 
res0: scala.concurrent.Future[Int] = [email protected] 

scala> res0.value 
res1: Option[scala.util.Try[Int]] = Some(Success(10)) 

recoverWith需要PartialFunction[Throwable,scala.concurrent.Future[A]],並返回一個Future[A]。你應該小心,因爲當它在這裏進行很多遞歸調用時它會使用相當多的內存。

+0

你的答案解決了我寫的完美無瑕的樣本。謝謝!但它並沒有完全解決我最初的問題,即使在成功時,我仍然需要繼續「遞歸」(不完全遞歸,我知道......)。所以我最終使用'和Then'。但你的回答引導我到那裏,所以再次感謝! –

1

隨着drexin回答關於異常處理和恢復的部分,讓我試着回答有關涉及未來的遞歸函數的部分。我相信使用Promise將幫助你實現你的目標。重組後的代碼應該是這樣的:

def win(prefix: String): String = { 
    val prom = Promise[String]() 

    def doWin(p:String) { 
     val futureGameLog = aPlayThatMayFail 
     futureGameLog.onComplete(t => t match { 
      case Success(diceRoll) => prom.success("%s, and finally, I won! I rolled %d !!!".format(prefix, diceRoll)) 
      case Failure(e) => e match { 
       case ex: LoserException => doWin("%s, and then i got %d".format(prefix, ex.diceRoll)) 
       case other => prom.failure(new Exception("%s, and then somebody cheated!!!".format(prefix))) 
      } 
     })   
    } 
    doWin(prefix) 
    Await.result(prom.future, someTimeout) 
} 

現在這不會是在這個意義上,它將一個長棧建立由於該期貨是異步真正的遞歸,但它類似於精神遞歸。使用這裏的承諾可以讓你阻止某些事情的發生,阻止調用者發生背後的事情。現在

,如果我這樣做,我會重新定義可能的事情,像這樣:

def win(prefix: String): Future[String] = { 
    val prom = Promise[String]() 

    def doWin(p:String) { 
     val futureGameLog = aPlayThatMayFail 
     futureGameLog.onComplete(t => t match { 
      case Success(diceRoll) => prom.success("%s, and finally, I won! I rolled %d !!!".format(prefix, diceRoll)) 
      case Failure(e) => e match { 
       case ex: LoserException => doWin("%s, and then i got %d".format(prefix, ex.diceRoll)) 
       case other => prom.failure(new Exception("%s, and then somebody cheated!!!".format(prefix))) 
      } 
     })   
    } 
    doWin(prefix) 
    prom.future 
} 

這樣你就可以推遲對是否阻止或使用異步回調這個函數的調用者的決定。這更加靈活,但它也讓調用者知道您正在進行異步計算,並且我不確定這對您的方案是否可接受。我會把這個決定留給你。

+0

謝謝!好答案...!!!我可以肯定地使用它:) –

1

這個工作對我來說:

def retryWithFuture[T](f: => Future[T],retries:Int, delay:FiniteDuration) (implicit ec: ExecutionContext, s: Scheduler): Future[T] ={ 
    f.recoverWith { case _ if retries > 0 => after[T](delay,s)(retryWithFuture[T](f , retries - 1 , delay)) } 
}