如果你不阻止未来,它会导致类似比赛的情况吗?

If you don't block on a future, can it cause a race like condition?

假设我有一个有未来调用的控制器,然后我重定向到另一个页面,该页面希望计算该调用。有没有可能未来 return 不够快,当我重定向到另一个页面时,数据不会 "fresh"?

示例:

class HomeController  {

  def action1 = Action{

    val f1 = Future { ... }
    Redirect(routes.HomeController.action2)
   }

  def action2 = Action {

     // expects the call to f1:Future to have executed
   }
}

我问的原因是我的服务层 return 应该是 Future 还是 block?或者我应该像在我的控制器中那样将 Future 传递给调用代码。

UserService
  save
  delete
  update
  findById
  etc. etc..

这些return未来应该吗?

如果我希望调用已经计算,我必须阻止。那么这有没有硬性规定呢?

当您的代码生效时,您的 action1 将启动将来执行的代码,然后将重定向发送到浏览器。不能保证以后的代码会在调用 action2 之前完成。

但是,假设这是 play 框架,您可以等待未来的代码完成而不会阻塞当前线程,方法是使用 Future 上的 map 或 onComplete 方法注册回调,您将在其中完成通过发送重定向请求。您需要将操作更改为 Action.async.

你的问题中有几处有点离题,所以我会依次解决它们

1) 重定向响应return会不会在以后的return之前?

无法判断,而且它的行为不会始终如一。对于在 Future 完成其计算之前重定向到 not return,您应该使用异步操作,以便在 Future 完成之前调用不会响应重定向:

def action1 = Action.async { request =>
  futureComputation().map { _ => Redirect(routes.HomeController.action2) }
}

2) 我的服务层 return 应该是 Future 吗?

简单的回答是是的,如果底层API也是非阻塞的

更微妙的答案是 是的,即使您的服务调用是阻塞的,但您工作流中使用的每个其他调用 return 都是未来 。这第二个资格是关于组成和可读性的。

假设工作流程是:

  • findById
  • fetchHttpResource(异步获取)
  • update(数据来自 fetch

因为只有一个异步组件,工作流应该是异步的,你可以这样写:

def workflow(id:UUID):Future[Unit] = {
  val user = UserService.findById(id)
  fetchHttpResource(id).map { resource =>
    val updatedUser = user.copy(x = resource)
    UserService.update(updatedUser)
  }
}

如果 UserService returns Future 也是,您可以使用 for-comprehension 代替:

def workflow(id: UUID): Future[Unit] = for {
  user <- UserService.findById(id)
  resource <- fetchHttpResource(id)
  _ <- UserService.update(user.copy(x = resource))
} yield {}

这让我想到

3) 使用 Future 是否有硬性规定?

异步代码是一个乌龟一路向下的事情。一旦你的链中有一个异步调用,你的链 到 return 一个 Future 一旦你有这个要求,通过 for 组合, mapflatMap 等,错误处理在 Future 签名中变得更加清晰。额外的好处是,如果您现在有一个阻塞调用,也许将来您可以找到一个异步 API 在该服务中使用,您的签名不必更改。

对于 return 阻止可能失败的调用的 Future,最好的策略是用 future {} 包装它们。但是,如果您计算的数据不会失败,则使用 Future.successful() 是更好的选择。