使用一系列依赖的future和non-future方法调用进行scala异常处理

时间:2018-03-30 18:55:35

标签: scala future

我有一个可能返回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())

  }    
}

1 个答案:

答案 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。有一些方法可以混合异步和同步异常处理,但在实践中很难分析和预测这种混合行为(您可能已经注意到了)。代码变得丑陋。