【问题标题】:When can a Future return an Exception that is not thrown from within the Future's body?Future 何时可以返回未从 Future 体内抛出的异常?
【发布时间】:2015-05-01 01:36:00
【问题描述】:

这里更普遍的问题是:在生产质量代码中,是否需要关注由 ExecutionContext 或其他并发基础设施产生的异常,在未来主体的执行之外?例如,如果线程池发生了一些故障,我是否会在将来看到一个异常返回并因此而无法执行?

这反过来又导致了如何对期货进行错误处理。我同意一般建议,即错误应该返回,而不是抛出,使用例如要么或 scallactic 的要么。但这似乎非常复杂,当调用未来时,需要考虑基础设施中的异常,即使其他一切都是以无异常或异常包装的方式编写的。但我不会就此征求意见——我认为这会使这篇文章“过于宽泛”而被关闭。 :=(

【问题讨论】:

    标签: scala concurrency future


    【解决方案1】:

    如果在执行Future 时发生异常但不在其主体中,我认为它不可能在Future 中返回。 Future.apply 的实现向执行者提交了一个PromiseCompletingRunnable。它看起来像这样:

    class PromiseCompletingRunnable[T](body: => T) extends Runnable {
      val promise = new Promise.DefaultPromise[T]()
    
      override def run() = {
        promise complete {
          try Success(body) catch { case NonFatal(e) => Failure(e) }
        }
      }
    }
    

    请注意,底层Promise 是使用执行Future.apply 主体的try/catch 块完成的。如果在上述Runnable 中发生异常(似乎不太可能),或者该try/catch 块之外的执行程序,它将不会被包装在返回的Future 中。如果 executor 内部发生异常,则更可能的情况是它是一些致命错误。更糟糕的是,这可能意味着 Future 永远不会完成。

    如果是导致问题的线程池,除了找到更好的线程池或找出潜在问题(线程太多?)之外,您真的无能为力。在运行时您可以做的甚至更少(如果有的话)。真的,这只是意味着底层并发 API 非常糟糕,使用了太多内存等等。

    【讨论】:

    • 我不愿狡辩,但我不明白 PromiseCompletingRunnable 如何阻止 ExecutionContext(广义上)将结果设置为它想要的任何值。另一方面,考虑到这个答案的其余部分和另一个,我觉得我不需要太在意——只要我不写自动驾驶仪或其他东西。
    • @EdStaub PromiseCompletingRunnable 中的 promise 是一个 val,您不能尝试多次完成 Promise。所以一旦run() 开始执行,执行者真的无法改变它的价值。你可以说也许执行者可以尝试先完成Promise,但我不知道有任何执行者甚至知道Promise 是什么,甚至。所以理论上,一些自定义执行器可以在执行 before 之前执行此操作,但实际上不能。标准库中的默认行为不这样做,也不应该这样做。
    • 谢谢。 FWIW,我最关心的是 run() 根本无法执行的情况,并且执行程序可能会通过填写自己的失败来表明这一点。
    • @EdStaub 在这种情况下,您将拥有一个永远不会完成的Future
    【解决方案2】:

    是和否……好吧,也许我们需要详细说明一下。

    如果我们谈论“生产质量代码”,即如果无法正常工作需要花费大量资金的代码,我们不能假设基础架构(运行时、库等)正常工作。因为他们没有。库有错误,即使它们不影响我们,它们也依赖于有限的资源,如内存、文件句柄以及并发的东西:线程。

    所以在某个时候应该意识到的一件事是:即使是最微不足道的代码行也可能会失败。这就是“是”部分:是的,您应该关注这种可能性。

    在No部分旁边:你写

    错误应该被返回,而不是被抛出,例如使用要么 要么 scalactic 的要么

    对于您所期望的异常情况也是如此。真正有可能失败的事情。说在 100 000 次处决中超过 1 次。 (是的,我从帽子里拿出了那个号码,我什至没有戴)。在这种情况下,添加显式异常处理是适当的做法,并且您提到的建议适用。

    但它不适用于所有其他可能的故障模式。如果您尝试使用这种方法来处理它们,您将无法在所有异常处理之间找到您的主要代码。而且你可能会在这个过程中添加很多错误。

    相反,请制定一些通用策略来处理代码中的任意异常。对于这些情况,例外是使用的正确工具。因为这个策略越简单越好,因为你真的不知道这段代码会在什么样的情况下被执行。

    Web 应用程序中的典型方法是围绕一个完整的请求设置一个 try catch 块,记录异常并返回一些错误页面。

    更精细的方法是像 Akka 这样的 Actor 框架,它有 Actor 来完成工作。如果这样的 Actor 抛出异常,Actor 将死亡并被新的 Actor 替换(通过各种方式来控制确切的行为)。

    与上述两种方法相结合的另一种方法是让您的应用程序的多个实例在不同的机器上运行,这样一台机器上的问题不会影响其他机器。

    正如您所见,在您的基础架构中处理异常的方法(包括但不限于执行您的 future 的库)与用于处理您的 future 的“正常”故障模式的异常处理非常不同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-05
      • 2011-09-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多