简介
为了在不阻塞当前进程的情况下执行一个效果,我们可以使用ZIO库中的纤维,它是一种轻量级的并发机制。
在ZIO库中,我们可以分叉任何IO[E, A]来立即产生一个UIO[Fiber[E, A]]。提供的Fiber可以用来加入光纤,这将在产生光纤的值时恢复,或者中断光纤,这将立即终止光纤并安全地释放光纤在ZIO库中获得的所有资源。
val example =
for {
fiber1 <- exampleData(data).fork // IO[E, example]
fiber2 <- validateData(data).fork // IO[E, Boolean]
// Do other stuff
valid <- fiber2.join
_ <- if (!valid) fiber1.interrupt
else IO.unit
example <- fiber1.join
} yield example
操作
fork和join
当我们需要启动一个光纤时,我们必须分叉一个效应,这就给我们一个光纤。这类似于启动一个Java线程或在Java线程池中添加一个新的线程,它是同一个概念。另外,加入是一种等待该光纤计算其值的方式。我们将等待,直到它完成。
import zio._
import zio.console._
import zio.clock._
import zio.duration._
for {
fiber <- (sleep(3.seconds) *>
putStrLn("Hello, after 3 second") *>
ZIO.succeed(10)).fork
_ <- putStrLn(s"Hello, to ZIO Library!")
res <- fiber.join
_ <- putStrLn(s"Our fiber succeeded with $res")
} yield ()
fork0
这个更强大的fork的变体,叫做fork0,允许指定监督者,这个监督者将从被分叉的光纤中传递不可恢复的错误,包括那些发生在finalizers中的错误。这个监督者需要被指定。如果没有,父光纤监督器将被使用,递归到根处理程序,它可以在Runtime中被指定(默认监督器只是打印堆栈跟踪)。
forkDaemon
使用ZIO#forkDaemon,效果是分叉到一个连接到全局范围的新光纤。这意味着,当之前执行返回效果的光纤终止时,分叉的光纤将继续运行。
在下面的例子中,我们有三个效果:内部、外部和myApp。使用ZIO#forkDaemon,外部效果分叉内部效果。在myApp效果中,内部光纤通过使用ZIO#fork方法被分叉,并在三秒后中断。在全局范围内fork,内层效果不会被中断,并继续做它的工作。
val inner = putStrLn("Inner job is running.")
.delay(1.seconds)
.forever
.onInterrupt(putStrLn("Inner job interrupted.").orDie)
val outer = (
for {
f <- inner.forkDaemon
_ <- putStrLn("Outer job is running.").delay(1.seconds).forever
_ <- f.join
} yield ()
).onInterrupt(putStrLn("Outer job interrupted.").orDie)
val myApp = for {
fiber <- outer.fork
_ <- fiber.interrupt.delay(3.seconds)
_ <- ZIO.never
} yield ()
中断
每当我们想移除我们的光纤时,我们可以简单地调用中断。直到所有的纤维终结者都被执行,并且中断操作已经完成或被中断,中断操作将不会恢复。这为构建程序提供了一种不泄漏资源的手段。
等待
你可以对光纤调用await来检查它是否成功或失败。一旦我们调用await,该光纤将被等待终止,我们将得到它的值作为一个Exit。一个成功或失败的值将被返回。
import zio.console._
import zio.random._
for {
booleanValue <- nextBoolean
fiber <- (if (booleanValue) ZIO.succeed(10) else ZIO.fail("The boolean was false")).fork
exitValue <- fiber.await
_ <- exitValue match {
case Exit.Success(value) => putStrLn(s"Fiber is succeeded with $value")
case Exit.Failure(cause) => putStrLn(s"Fiber is failed")
}
} yield ()
在ZIO库中,await ,与join相似,但比join低级。每当我们调用join时,如果底层的光纤失败了,我们在试图连接它时也会遇到同样的错误。
平行性
zipPar方法可以用来并行地执行动作。
def largeCompute(m1: Matrix, m2: Matrix, v: Matrix): UIO[Matrix] =
for {
t <- computeInverse(m1).zipPar(computeInverse(m2))
(i1, i2) = t
r <- applyMatrices(i1, i2, v)
} yield r
竞速
在ZIO光纤中,两个IO动作可以进行竞赛,这意味着它们将并行执行,第一个成功完成的动作的值将被返回。
result(100) race result(200)
在zio fiber中,竞赛组合器是资源安全的,这意味着如果两个动作中的一个返回一个值,另一个将中断,以防止浪费资源。
总结
通过使用ZIO,我们只成功地发掘了它的一小部分潜力--通过在效果中使用ZIO Fibers。开箱即用的并行性、不可更改性、参考透明性和包裹式的副作用管理,使得这个例子的编写不费吹灰之力,而且非常愉快。