我有一个可能返回Future
的方法 - 成功或失败,甚至可以抛出异常。我可以通过在整个方法上放置try catch块并一直返回Future来避免这种情况,但我现在想避免它。调用这种方法我几乎没有问题:
1)在调用者代码中,如果我使用map
,我期望执行一个方法并期望一个Future或异常,我试图按以下方式处理:
object ETLCoordinator {
private def getBusinessListFromModules(modulePaths: Iterable[File]) : Future[String] = {
implicit val ec = ExecutionContext.global
println("Inside getBusinessListFromModules..")
throw new java.lang.RuntimeException("failed to get businesses") //Exception is thrown before future was constructed
Future("ok")
}
def main(args: Array[String]) {
println("Inside Future Test..")
implicit val ec = ExecutionContext.global
val modulePaths = Iterable(new File("mdrqaint/MDR/QAINT/QAINTX"))
val fut1 = getBusinessListFromModules(modulePaths) //This is outside of try and which should be okay
try {
fut1.map { res =>
println("things after Successful fut1")
}.recover{
case t: Throwable => println("Failed future in fut1: "+ t.getMessage)
}
} catch {
case t: Throwable => println("Exception in fut1: "+ t.getMessage)
}
}
}
输出:(上面没有执行recover或catch块)
Inside Future Test..
Inside getBusinessListFromModules..
Exception in thread "main" java.lang.RuntimeException: failed to get businesses
但是如果我把val fut1 = getBusinessListFromModules(modulePaths)
放在Try块中,那么Exception会被Catch块捕获并且我得到输出:
Inside Future Test..
Inside getBusinessListFromModules..
Exception in fut1: failed to get businesses
这是为什么?我虽然在调用map,flatmap,onSuccess,onComplete等方法时会执行Future执行。在这种情况下,调用map
已经在Try块中。
2)定义和调用此类方法的更好方法是什么?在调用者中尝试/ catch块或者尝试/ catch方法本身?或任何其他方式。我尝试在Future中包装调用方法,所以我在调用者中得到Future [Future [String]]。我能够避免所有的尝试。
val fut1 = Future(getBusinessListFromModules(modulePaths))
//try {
fut1.map { res =>
res.map{ str =>
println("things after Successful fut1")
}.recover{
case t: Throwable => println("Failed in future of fut1: "+ t.getMessage)
}
println("things after Successful fut1 wrapper")
}.recover{
case t: Throwable => println("Failed to create future in fut1: "+ t.getMessage)
}
3)如果中间有另一种方法,它授权给getBusinessListFromModules
,但它本身就是非未来方法。
object ETLController {
private def getBusinessListFromModules(modulePaths: Iterable[File]) : Future[String] = {
implicit val ec = ExecutionContext.global
println("Inside getBusinessListFromModules..")
//throw new java.lang.RuntimeException("failed to get businesses")
Future("ok")
}
private def callGetBusList(modulePaths: Iterable[File]) : String = {
implicit val ec = ExecutionContext.global
val etlF = getBusinessListFromModules(modulePaths)
etlF onComplete {
case Success(itr) => {
println("Future getBusinessListFromModules success: "+ itr)
throw new java.lang.RuntimeException("RTE from callGetBusList")
}
case Failure(t) => {
println("Future getBusinessListFromModules throws an error")
}
}
"callGetBusList was a success"
}
def main(args: Array[String]) {
println("Inside Future Test..")
implicit val ec = ExecutionContext.global
val modulePaths = Iterable(new File("mdrqaint/MDR/QAINT/QAINTX"))
try {
val fut = Future(callGetBusList(modulePaths))
fut.map { res =>
println("successful future!")
}.recover{
case t: Throwable => println("Failed future: "+ t.getMessage)
}
} catch {
case t: Throwable => println("callGetBusList failed:" + t.getMessage)
}
}
}
输出:(没有恢复或捕获块执行!)
Inside Future Test..
Inside getBusinessListFromModules..
Future getBusinessListFromModules success: ok
java.lang.RuntimeException: RTE from callGetBusList
at ..
successful future!
我甚至尝试双重包装Future调用:
val fut = Future(Future(callGetBusList(modulePaths)))
fut.map { res =>
res.map { str =>
println("successful inner future! "+ str)
}.recover{
case t: Throwable => println("Failed inner future: "+ t.getMessage)
}
println("successful outer future!")
}.recover{
case t: Throwable => println("Failed outer future: "+ t.getMessage)
}
输出:
Future getBusinessListFromModules success: ok
java.lang.RuntimeException: RTE from callGetBusList
at
successful inner future! callGetBusList was a success
successful outer future!
我得到" callGetBusList取得了成功"在RuntimeException
方法中看起来onComplete
似乎丢失了!如何在最终来电者中捕获它?处理此类未来依赖关系的更好做法是什么?
更新: 基于@dk14解释,选择转换中间方法返回Future,基本上所有方法都返回某种Future而不是简单的Exception。
object ETLController {
private def getBusinessListFromModules(modulePaths: Iterable[File]) : Future[String] = {
implicit val ec = ExecutionContext.global
println("Inside getBusinessListFromModules..")
Future {
Thread.sleep(2000)
throw new java.lang.RuntimeException("failed to get businesses")
"ok"
}
}
private def callGetBusList(modulePaths: Iterable[File]) : Future[String] = {
implicit val ec = ExecutionContext.global
val etlF = getBusinessListFromModules(modulePaths)
etlF map { itr =>
println("Future getBusinessListFromModules success: "+ itr)
throw new java.lang.RuntimeException("RTE from callGetBusList")
} recover {
case t: Throwable => {
println("Future callGetBusList throws an error: " + t.getMessage)
throw t
}
}
}
def main(args: Array[String]) {
println("Inside Future Test..")
implicit val ec = ExecutionContext.global
val modulePaths = Iterable(new File("mdrqaint/MDR/QAINT/QAINTX"))
val fut = callGetBusList(modulePaths)
fut.map { str =>
println("successful future! "+ str)
}.recover{
case t: Throwable => println("Failed future: "+ t.getMessage)
}
println("Active threads: " +Thread.activeCount())
sys.allThreads().foreach(t => t.join())
}
}
答案 0 :(得分:2)
1)期货正在热切地开火,他们aren't referentially transparent。
引用问题的答案还包含有关Future
内部行为的一些见解,因此我想在此处跳过它。
为了以更可预测的方式管理有关执行池/队列/线程的副作用,您可以考虑scalaz / monix / fs2 Task
或{ {3}} / scalaz / cats Eval
(更抽象的延迟评估,用于同步内容)+ iteratee(继续抽象在订阅上)作为替代。所有这些都是引用透明的并且懒得开始执行"按需"。
2)最好的方法是你不喜欢的方式:不要抛出Future上下文的异常。
您可能还会考虑flatMap
以避免Future[Future[T]]
3)双包裹期货直接a-la Future(Future(...))
不会改变任何东西。无论返回什么,您的方法都在val etlF = g...
(在同一个线程中)执行。 Future("ok")
的内容(lambda)在另一个线程上急切地执行("小"不可预测的延迟),但[执行任务正在提交到池]仍然在{{1 }}。
一种解决方法(不是真正推荐的)是getBusinessListFromModules
,它会让您回到未来直接来自val etlF = Future(getBusinessListFromModules(...)).flatMap(identity)
并间接来自getBusinessListFromModules
的内部{{1 }}。
最好重构getBusinessListFromModules
本身,但是也会针对您的方法可能遇到的各种麻烦(验证,同步与异步等等)引入不同的异常类型。
P.S。有一些方法可以混合异步和同步异常处理,但在实践中很难分析和预测这种混合行为(您可能已经注意到了)。代码变得丑陋。