关于Future
顺序运行与并行运行:
这有点棘手,因为 ScalaFuture
被设计为渴望。各种 Scala 库中还有一些其他构造可以处理同步和异步效果,例如cat 、IO
Monix等,它们是以惰性方式设计的,它们没有这种行为。Task
ZIO
急切的事情Future
是它会尽快开始计算。这里的“开始”意味着将它安排在一个ExecutionContext
明确选择或隐含存在的地方。虽然从技术上讲,如果调度程序决定这样做,执行可能会暂停一点,但它很可能几乎是立即开始的。
所以如果你有一个 type 的值Future
,它就会开始运行。如果你有一个惰性类型的值Future
,或者一个返回类型值的函数/方法Future
,那么它不是。
但是,即使您拥有的只是简单的值(没有惰性 val 或 defs),如果Future
定义是在 for-comprehension 中完成的,那么这意味着它是 monadic flatMap 链的一部分(如果您不理解这一点,请忽略它)现在),它将按顺序运行,而不是并行运行。为什么?这不是特定于Future
s 的;每个 for-comprehension 都具有作为顺序链的语义,您可以在其中将上一步的结果传递给下一步。因此,如果它依赖于步骤 n 中的某些内容,那么您不能在步骤 n + 1 中运行某些内容是合乎逻辑的。
这里有一些代码来证明这一点。
val program = for {
_ <- Future { Thread.sleep(5000); println("f1") }
_ <- Future { Thread.sleep(5000); println("f2") }
} yield ()
Await.result(program, Duration.Inf)
该程序将等待五秒钟,然后打印“f1”,然后再等待五秒钟,然后打印“f2”。
现在让我们来看看这个:
val f1 = Future { Thread.sleep(5000); println("f1") }
val f2 = Future { Thread.sleep(5000); println("f2") }
val program = for {
_ <- f1
_ <- f2
} yield ()
Await.result(program, Duration.Inf)
但是,该程序将在五秒钟后同时打印“f1”和“f2”。
请注意,在第二种情况下并没有真正违反序列语义。f2
仍然有机会使用 的结果f1
。但是f2
没有使用f1
;的结果 它是一个可以立即计算的独立值(用 a 定义val
)。因此,如果我们更改val f2
为一个函数,例如def f2(number: Int)
,那么执行会更改:
val f1 = Future { Thread.sleep(5000); println("f1"); 42 }
def f2(number: Int) = Future { Thread.sleep(5000); println(number) }
val program = for {
number <- f1
_ <- f2(number)
} yield ()
如您所料,这将在五秒后打印“f1”,然后另一个才会Future
启动,因此它将在再过五秒后打印“42”。
关于交易:
正如评论中提到的@cbley,这听起来像是您想要数据库事务。例如,在 SQL 数据库中,这具有非常特定的含义,它确保了ACID 属性。
如果这是你需要的,你需要在数据库层解决它。Future
太笼统了;它只是一种模拟同步和异步计算的效果类型。当您看到一个Future
值时,仅通过查看类型,您无法判断它是数据库调用的结果,还是某个 HTTP 调用的结果。
例如,doobie将每个数据库查询描述为一种ConnectionIO
类型。您可以将多个查询排成一列以供理解,就像您使用以下内容一样Future
:
val program = for {
a <- database.getA()
_ <- database.write("foo")
b <- database.getB()
} yield {
// use a and b
}
但与我们之前的示例不同,这里getA()
andgetB()
不返回 type 的值Future[A]
,而是ConnectionIO[A]
。很酷的是,doobie 完全处理了您可能希望这些查询在单个事务中运行的事实,因此如果getB()
失败,“foo”将不会提交到数据库。
因此,在这种情况下,您要做的是获取查询集的完整描述,将其包装program
为 type的单个值ConnectionIO
,一旦您想要实际运行事务,您将执行类似的操作program.transact(myTransactor)
,其中myTransactor
的实例在哪里Transactor
,一个知道如何连接到您的物理数据库的doobie 构造。
一旦你进行交易,你ConnectionIO[A]
的就变成了Future[A]
. 如果事务失败,您将有一个 failed Future
,并且不会真正向您的数据库提交任何内容。
如果您的数据库操作彼此独立并且可以并行运行,doobie 也将允许您这样做。通过 doobie 按顺序和并行提交事务在 docs 中有很好的解释。