什么时候 Future return 不是从 Future 体内抛出的异常?

When can a Future return an Exception that is not thrown from within the Future's body?

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

这反过来又导致了应该如何处理 futures 的错误。我接受一般建议,即应该返回错误,而不是抛出错误,例如使用Either 或 scalactic 的 Or。但是,如果在调用 future 时需要考虑基础架构的异常,即使其他所有内容都是以无异常或异常包装的方式编写的,这似乎非常复杂。但我不会就此寻求建议 - 我认为它会使 post 关闭为 "too broad"。 :=(

如果在执行 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 中。如果执行程序中发生了一些异常,更可能的情况是它是一些致命错误。更糟糕的是,这可能意味着 Future 永远不会完成。

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

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

如果我们谈论 "production-quality code",即如果代码不起作用则需要花费大量资金,我们不能假设基础设施(运行时间、库等)只是工作。因为他们没有。库有错误,即使它们不影响我们,它们也依赖于有限的资源,如内存、文件句柄以及并发性的东西:线程。

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

在否部分旁边:您写

errors should be returned, not thrown, using e.g. either Either or scalactic's Or

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

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

相反,制定一些通用策略来处理代码中的任意异常。对于这些情况,异常是正确的使用工具。对于此策略,越简单越好,因为您真的不知道这段代码将在何种情况下执行。

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

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

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

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