我们有一个Scala Play webapp,它在HTTP请求中执行许多数据库操作,每个操作都是Future。通常我们将Futures冒泡到异步控制器动作并让Play处理等待它们。
但我也注意到,在很多地方,我们不会冒泡未来甚至等待它完成。我认为这很糟糕,因为这意味着如果未来失败,HTTP请求不会失败,但它实际上甚至会保证将来会执行,因为没有什么会等待它的结果?在提供HTTP请求后,Play会丢弃期待已久的未来,还是让它们在后台运行?
答案 0 :(得分:2)
TL; DR
Future
。Future
的任何一个失败,将不会报告错误。长版
发送HTTP响应后,您的未来不会被杀死。你可以这样自己试试:
def futuresTest = Action.async { request =>
println(s"Entered futuresTest at ${LocalDateTime.now}")
val ignoredFuture = Future{
var i = 0
while (i < 10) {
Thread.sleep(1000)
println(LocalDateTime.now)
i += 1
}
}
println(s"Leaving futuresTest at ${LocalDateTime.now}")
Future.successful(Ok)
}
但是,如果任何期货失败,请求不会失败。如果这是一个问题,那么您可以使用 for comprehension 或 flatMaps 撰写期货。以下是您可以做的一个示例(我假设您的期货只执行副作用(Future[Unit]
)
让您的期货以并列方式执行
val dbFut1 = dbCall1(...)
val dbFut2 = dbCall2(...)
val wsFut1 = wsCall1(...)
val fut = for(
_ <- dbFut1;
_ <- dbFut2;
_ <- wsFut1
) yield ()
fut.map(_ => Ok)
让它们按顺序执行
val fut = for(
_ <- dbCall1(...);
_ <- dbCall2(...);
_ <- wsCall2(...)
) yield ()
fut.map(_ => Ok)
答案 1 :(得分:1)
它实际上甚至保证将来会被执行, 既然没有什么可以等待它的结果呢?将播放下降 在HTTP请求被提供或离开之后,期待已久的未来 他们在后台跑?
这个问题实际上比Play更深入。你通常会问:“如果我不同步等待未来,我怎么能保证它不会在没有GC的情况下完成?”。要回答这个问题,我们需要了解GC实际上如何查看线程。从GC的角度来看,一个线程就是我们所说的“根”。这样的根是堆遍历其对象的起点,并查看哪些对象有资格进行收集。根中还有静态字段,例如,已知它们在应用程序的整个生命周期中都存在。
所以,当你这样看的时候,想一想Future
实际上做了什么,这是一个在一个专用线程上运行的函数,该线程来自可通过底层ExecutorService
获得的线程池(我们在Scala中将其称为ExecutionContext
),您会看到即使您没有等待完成,JVM运行时仍可确保您的Future
将运行完成。对于包装函数的Future
对象,它保存对未完成的函数体的引用,因此不会收集Future
本身。
当你从这个角度思考它时,它是完全合乎逻辑的,因为Future
的执行是异步发生的,我们通常使用map
之类的延续以异步方式继续处理它, flatMap
,onComplete
等