首页 文章

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

提问于
浏览
0

我有一个方法可能会返回 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 was a success"好像 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 回答

  • 2

    1)期货急切地开火,他们aren't referentially transparent . 引用问题的答案也包含有关 Futureinternal behavior 的一些见解,所以我想在此处跳过它 .

    为了以更可预测的方式管理有关执行池/队列/线程的副作用,您可以考虑scalaz / monix / fs2 Taskiteratee / scalaz / cats Eval (更抽象的懒惰评估,用于同步内容)Cont(继续是抽象的订阅)作为替代 . 所有都是引用透明的并且懒惰地开始执行"on-demand" .

    2)最好的方法是你不喜欢的方式:不要抛出Future上下文的异常 .

    你也可以考虑 flatMap 以避免 Future[Future[T]]

    3)双包装期货直接a-la Future(Future(...)) 不会改变任何东西 . 无论返回什么,您的方法都在 val etlF = g... (在同一个线程中)执行 . Future("ok") 的内容(lambda)在另一个线程上急切地执行(具有"small"不可预测的延迟)但[执行任务正在提交到池]仍在 getBusinessListFromModules 内 .

    一个解决方法(不是真正推荐的)是 val etlF = Future(getBusinessListFromModules(...)).flatMap(identity) ,这将使您回到未来包装任何直接来自 getBusinessListFromModules 并且间接来自 getBusinessListFromModules 的内部 Future 的异常 .

    最好重构 getBusinessListFromModules 本身,但是也会为你的方法可能遇到的各种麻烦(验证,同步和异步等等)引入不同的异常类型 .

    附:有一些方法可以混合异步和同步异常处理,但实际上很难分析和预测这种混合行为(您可能已经注意到了) . 代码变得丑陋 .

相关问题