FunDA(7)- Reactive Streams to fs2 Pull Streams
Reactive-Stream不只是简单的push-model-stream, 它还带有“拖式”(pull-model)性质。这是因为在Iteratee模式里虽然理论上由Enumerator负责主动推送数据,实现了push-model功能。但实际上Iteratee也会根据自身情况,通过提供callback函数通知Enumerator可以开始推送数据,这从某种程度上也算是一种pull-model。换句话讲Reactive-Streams是通过push-pull-model来实现上下游Enumerator和Iteratee之间互动的。我们先看个简单的Iteratee例子:
def showElements: Iteratee[Int,Unit] = Cont {
case Input.El(e) =>
println(s"EL($e)")
showElements
case Input.Empty => showElements
case Input.EOF =>
println("EOF")
Done((),Input.EOF)
} //> showElements: => play.api.libs.iteratee.Iteratee[Int,Unit]
val enumNumbers = Enumerator(,,,,) //> enumNumbers : play.api.libs.iteratee.Enumerator[Int] = play.api.libs.iteratee.Enumerator$$anon$19@47f6473 enumNumbers |>> showElements //> EL(1)
//| EL(2)
//| EL(3)
//| EL(4)
//| EL(5)
//| res0: scala.concurrent.Future[play.api.libs.iteratee.Iteratee[Int,Unit]] = Success(Cont(<function1>))
我们看到:enumNumbers |>> showElements立刻启动了运算。但并没有实际完成数据发送,因为showElements并没有收到Input.EOF。首先,我们必须用Iteratee.run来完成运算:
val it = Iteratee.flatten(enum |>> consumeAll).run//> El(1)
//| El(2)
//| El(3)
//| El(4)
//| El(5)
//| El(6)
//| El(7)
//| El(8)
//| EOF
//| it : scala.concurrent.Future[Int] = Success(99)
这个run函数是这样定义的:
/**
* Extracts the computed result of the Iteratee pushing an Input.EOF if necessary
* Extracts the computed result of the Iteratee, pushing an Input.EOF first
* if the Iteratee is in the [[play.api.libs.iteratee.Cont]] state.
* In case of error, an exception may be thrown synchronously or may
* be used to complete the returned Promise; this indeterminate behavior
* is inherited from fold().
*
* @return a [[scala.concurrent.Future]] of the eventually computed result
*/
def run: Future[A] = fold({
case Step.Done(a, _) => Future.successful(a)
case Step.Cont(k) => k(Input.EOF).fold({
case Step.Done(a1, _) => Future.successful(a1)
case Step.Cont(_) => sys.error("diverging iteratee after Input.EOF")
case Step.Error(msg, e) => sys.error(msg)
})(dec)
case Step.Error(msg, e) => sys.error(msg)
})(dec)
再一个问题是:enumNumbers |>> showElements是个封闭的运算,我们无法逐部分截取数据流,只能取得整个运算结果。也就是说如果我们希望把一个Enumerator产生的数据引导到fs2 Stream的话,只能在所有数据都读入内存后才能实现了。这样就违背了使用Reactive-Streams的意愿。那我们应该怎么办?一个可行的方法是使用一个存储数据结构,用两个线程,一个线程里Iteratee把当前数据存入数据结构,另一个线程里fs2把数据取出来。fs2.async.mutable包提供了个Queue类型,我们可以用这个Queue结构来作为Iteratee与fs2之间的管道:Iteratee从一头把数据压进去(enqueue),fs2从另一头把数据取出来(dequeue)。
我们先设计enqueue部分,这部分是在Iteratee里进行的:
def enqueueTofs2(q: async.mutable.Queue[Task,Option[Int]]): Iteratee[Int,Unit] = Cont {
case Input.EOF =>
q.enqueue1(None).unsafeRun
Done((),Input.EOF)
case Input.Empty => enqueueTofs2(q)
case Input.El(e) =>
q.enqueue1(Some(e)).unsafeRun
enqueueTofs2(q)
} //> enqueueTofs2: (q: fs2.async.mutable.Queue[fs2.Task,Option[Int]])play.api.libs.iteratee.Iteratee[Int,Unit]
先分析一下这个Iteratee:我们直接把enqueueTofs2放入Cont状态,也就是等待接受数据状态。当收到数据时运行q.enqueue1把数据塞入q,然后不断循环运行至收到Input.EOF。注意:q.enqueue1(Some(e)).unsafeRun是个同步运算,在未成功完成数据enqueue1的情况下会一直占用线程。所以,q另一端的dequeue部分必须是在另一个线程里运行,否则会造成整个程序的死锁。fs2的Queue类型款式是:Queue[F,A],所以我们必须用Stream.eval来对这个Queue进行函数式的操作:
val fs2Stream: Stream[Task,Int] = Stream.eval(async.boundedQueue[Task,Option[Int]]()).flatMap { q =>
//run Enumerator-Iteratee and enqueue data in thread 1
//dequeue data and en-stream in thread 2(current thread)
}
因为Stream.eval运算结果是Stream[Task,Int],所以我们可以得出这个flatMap内的函数款式 Queue[Task,Option[Int]] => Stream[Task,Int]。下面我们先考虑如何实现数据enqueue部分:这部分是通过Iteratee的运算过程产生的。我们提到过这部分必须在另一个线程里运行,所以可以用Task来选定另一线程如下:
Task { Iteratee.flatten(enumerator |>> pushData(q)).run }.unsafeRunAsyncFuture()
现在这个Task就在后面另一个线程里自己去运算了。但它的运行进展则会依赖于另一个线程中dequeue数据的进展。我们先看看fs2提供的两个函数款式:
/** Repeatedly calls `dequeue1` forever. */
def dequeue: Stream[F, A] = Stream.bracket(cancellableDequeue1)(d => Stream.eval(d._1), d => d._2).repeat /**
* Halts the input stream at the first `None`.
*
* @example {{{
* scala> Stream[Pure, Option[Int]](Some(1), Some(2), None, Some(3), None).unNoneTerminate.toList
* res0: List[Int] = List(1, 2)
* }}}
*/
def unNoneTerminate[F[_],I]: Pipe[F,Option[I],I] =
_ repeatPull { _.receive {
case (hd, tl) =>
val out = Chunk.indexedSeq(hd.toVector.takeWhile { _.isDefined }.collect { case Some(i) => i })
if (out.size == hd.size) Pull.output(out) as tl
else if (out.isEmpty) Pull.done
else Pull.output(out) >> Pull.done
}}
刚好,dequeue产生Stream[F,A]。而unNoneTerminate可以根据Stream(None)来终止运算。现在我们可以把这个Reactive-Streams到fs2-pull-streams转换过程这样来定义:
implicit val strat = Strategy.fromFixedDaemonPool()
//> strat : fs2.Strategy = Strategy
val fs2Stream: Stream[Task,Int] = Stream.eval(async.boundedQueue[Task,Option[Int]]()).flatMap { q =>
Task(Iteratee.flatten(enumNumbers |>> enqueueTofs2(q)).run).unsafeRunAsyncFuture
pipe.unNoneTerminate(q.dequeue)
} //> fs2Stream : fs2.Stream[fs2.Task,Int] = attemptEval(Task).flatMap(<function1>).flatMap(<function1>)
现在这个stream应该已经变成fs2.Stream[Task,Int]了。我们可以用前面的log函数来试运行一下:
def log[A](prompt: String): Pipe[Task,A,A] =
_.evalMap {row => Task.delay{ println(s"$prompt> $row"); row }}
//> log: [A](prompt: String)fs2.Pipe[fs2.Task,A,A] fs2Stream.through(log("")).run.unsafeRun //> > 1
//| > 2
//| > 3
//| > 4
//| > 5
我们成功的把Iteratee的Reactive-Stream转化成fs2的Pull-Model-Stream。
下面是这次讨论的源代码:
import play.api.libs.iteratee._
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.collection.mutable._
import fs2._
object iteratees {
def showElements: Iteratee[Int,Unit] = Cont {
case Input.El(e) =>
println(s"EL($e)")
showElements
case Input.Empty => showElements
case Input.EOF =>
println("EOF")
Done((),Input.EOF)
}
val enumNumbers = Enumerator(,,,,) enumNumbers |>> showElements Iteratee.flatten(enumNumbers |>> showElements).run def enqueueTofs2(q: async.mutable.Queue[Task,Option[Int]]): Iteratee[Int,Unit] = Cont {
case Input.EOF =>
q.enqueue1(None).unsafeRun
Done((),Input.EOF)
case Input.Empty => enqueueTofs2(q)
case Input.El(e) =>
q.enqueue1(Some(e)).unsafeRun
enqueueTofs2(q)
}
implicit val strat = Strategy.fromFixedDaemonPool()
val fs2Stream: Stream[Task,Int] = Stream.eval(async.boundedQueue[Task,Option[Int]]()).flatMap { q =>
Task(Iteratee.flatten(enumNumbers |>> enqueueTofs2(q)).run).unsafeRunAsyncFuture
pipe.unNoneTerminate(q.dequeue)
} def log[A](prompt: String): Pipe[Task,A,A] =
_.evalMap {row => Task.delay{ println(s"$prompt> $row"); row }} fs2Stream.through(log("")).run.unsafeRun }
FunDA(7)- Reactive Streams to fs2 Pull Streams的更多相关文章
- FunDA(5)- Reactive Streams:Play with Iteratees
FunDA的设计目标就是把后台数据库中的数据搬到内存里,然后进行包括并行运算的数据处理,最后可能再对后台数据库进行更新.如果需要把数据搬到内存的话,那我们就必须考虑内存是否能一次性容纳所有的数据,有必 ...
- FunDA(6)- Reactive Streams:Play with Iteratees、Enumerator and Enumeratees
在上一节我们介绍了Iteratee.它的功能是消耗从一些数据源推送过来的数据元素,不同的数据消耗方式代表了不同功能的Iteratee.所谓的数据源就是我们这节要讨论的Enumerator.Enumer ...
- FunDA(9)- Stream Source:reactive data streams
上篇我们讨论了静态数据源(Static Source, snapshot).这种方式只能在预知数据规模有限的情况下使用,对于超大型的数据库表也可以说是不安全的资源使用方式.Slick3.x已经增加了支 ...
- FunDA(14)- 示范:并行运算,并行数据库读取 - parallel data loading
FunDA的并行数据库读取功能是指在多个线程中同时对多个独立的数据源进行读取.这些独立的数据源可以是在不同服务器上的数据库表,又或者把一个数据库表分成几个独立部分形成的独立数据源.当然,并行读取的最终 ...
- FunDA(8)- Static Source:保证资源使用安全 - Resource Safety
我们在前面用了许多章节来讨论如何把数据从后台数据库中搬到内存,然后进行逐行操作运算.我们选定的解决方案是把后台数据转换成内存中的数据流.无论在打开数据库表或从数据库读取数据等环节都涉及到对数据库表这项 ...
- FunDA(0)- Functional Data Access accessible to all
大数据.多核CPU驱动了函数式编程模式的兴起.因为函数式编程更适合多线程.复杂.安全的大型软件编程.但是,对许多有应用软件开发经验的编程者来说,函数式编程模式是一种全新的.甚至抽象的概念,可能需要很长 ...
- FunDA(3)- 流动数据行操作:FDAPipeLine operations using scalaz-stream-fs2
在上节讨论里我们介绍了数据行流式操作的设想,主要目的是把后台数据库的数据载入前端内存再拆分为强类型的数据行,这样我们可以对每行数据进行使用和处理.形象点描述就是对内存里的一个数据流(data-stre ...
- FunDA(17)- 示范:异常处理与事后处理 - Exceptions handling and Finalizers
作为一个能安全运行的工具库,为了保证占用资源的安全性,对异常处理(exception handling)和事后处理(final clean-up)的支持是不可或缺的.FunDA的数据流FDAPipeL ...
- FunDA(15)- 示范:任务并行运算 - user task parallel execution
FunDA的并行运算施用就是对用户自定义函数的并行运算.原理上就是把一个输入流截分成多个输入流并行地输入到一个自定义函数的多个运行实例.这些函数运行实例同时在各自不同的线程里同步运算直至耗尽所有输入. ...
随机推荐
- PAT 1047 编程团体赛(代码)
1047 编程团体赛(20)(20 分) 编程团体赛的规则为:每个参赛队由若干队员组成:所有队员独立比赛:参赛队的成绩为所有队员的成绩和:成绩最高的队获胜. 现给定所有队员的比赛成绩,请你编写程序找出 ...
- mongodb的安装配置方法
安装方法: https://docs.mongodb.com/manual/tutorial/install-mongodb-enterprise-on-red-hat/ 使用向导: https:// ...
- 设计模式之Adapter设计模式
这个设计模式是我这两天刚学的,这儿算是我的读书笔记发布出来是供大家一起学习,后面有我自己的感悟,下面是我网上整理的 以下情况使用适配器模式 • 你想使用一个已经存在的类,而它的接口不符合你的需求. • ...
- 编译 link
--generating dsym file change the appropriate one from 'DWARF with dSYM file' to just 'DWARF',This s ...
- 【commons-io】File对文件与目录的处理&FileUtis,IOUtils,FilenameUtils工具的使用
-------------------File的使用-------------- 1.File类对文件的处理 1.1目录结构: 1.2测试对文件Test.txt处理: // 测试文件 @Test p ...
- python数据类型2
一 文件格式补充 在python3中,除字符串外,所有数据类型在内存中的编码格式都是utf-8,而字符串在内存中的格式是Unicode的格式. 由于Unicode的格式无法存入硬盘中,所以这里还有一种 ...
- python的基础操作2
一 字符串格式化 占位符 %s和%d %s是属于字符串的占位符,而%d是属于数字类型的占位符 #占位符 %s %d # a="我叫%s,年龄%d,就是一个%s"%("al ...
- 2018.10.23 bzoj1297: [SCOI2009]迷路(矩阵快速幂优化dp)
传送门 矩阵快速幂优化dp简单题. 考虑状态转移方程: f[time][u]=∑f[time−1][v]f[time][u]=\sum f[time-1][v]f[time][u]=∑f[time−1 ...
- UVaLive 4597 Inspection (网络流,最小流)
题意:给出一张有向图,每次你可以从图中的任意一点出发,经过若干条边后停止,然后问你最少走几次可以将图中的每条边都走过至少一次,并且要输出方案,这个转化为网络流的话,就相当于 求一个最小流,并且存在下界 ...
- 整理iOS9适配中出现的坑
一.NSAppTransportSecurity iOS9让所有的HTTP默认使用了HTTPS,原来的HTTP协议传输都改成TLS1.2协议进行传输.直接造成的情况就是App发请求的时候弹出网络无法连 ...