FunDA(8)- Static Source:保证资源使用安全 - Resource Safety
我们在前面用了许多章节来讨论如何把数据从后台数据库中搬到内存,然后进行逐行操作运算。我们选定的解决方案是把后台数据转换成内存中的数据流。无论在打开数据库表或从数据库读取数据等环节都涉及到对数据库表这项资源的安全使用:我们最起码要保证在完成使用或者使用中途出现错误异常退出时能释放占用的资源。谈到资源使用安全,不得不想到函数式编程通用的那个bracket函数,fs2同样提供了这个函数:
def bracket[F[_],R,A](r: F[R])(use: R => Stream[F,A], release: R => F[Unit]): Stream[F,A] = Stream.mk {
StreamCore.acquire(r, release andThen (Free.eval)) flatMap { case (_, r) => use(r).get }
}
这个函数的入参数r,use,release都涉及到了资源占用处理:r一般是打开文件或者库表操作,use是资源使用如读取数据过程,release 顾名思义就是正常完成资源使用后的资源释放清理过程。函数bracket能保证这些过程的正确引用。
我们用几个例子来分析一下这个函数的功能:
val s = Stream.bracket(Task.delay(throw new Exception("Oh no!")))(
_ => Stream(,,) ++ Stream.fail(new Exception("boom!")) ++ Stream(,),
_ => Task.delay(println("normal end")))
s.runLog.unsafeRun //> java.lang.Exception: Oh no!
//| at demo.ws.streams$$anonfun$main$1$$anonfun$1.apply(demo.ws.streams.scal
//| a:4)
//| at demo.ws.streams$$anonfun$main$1$$anonfun$1.apply(demo.ws.streams.scal
//| a:4)
在上面这个例子里我们人为在两个地方制造了异常。我们可以用onError来截获这些异常:
val s1 = s.map(_.toString).onError {e => Stream.emit(e.getMessage)}
s1.runLog.unsafeRun //> res0: Vector[String] = Vector(Oh no!)
必须用toString转换了Stream元素类型后才能把截获的异常信息放进Stream。注意release未调用,因为资源还没有被占用。但是如果除了释放资源外还有其它清理工作的话,我们可以用onFinalize来确保一定可以调用清理程序:
val s5 = s1.onFinalize(Task.delay{println("finally end!")})
s5.runLog.unsafeRun //> finally end!
//| res1: Vector[String] = Vector(Oh no!)
如果在使用资源中间出现异常会怎样?
val s3 = Stream.bracket(Task.delay())(
_ => Stream(,,) ++ Stream.fail(new Exception("boom!")) ++ Stream(,),
_ => Task.delay(println("normal end")))
val s4 = s3.map(_.toString).onError {e => Stream.emit(e.getMessage)}
.onFinalize(Task.delay{println("finally end!")}) s4.runLog.unsafeRun //> normal end
//| finally end!
//| res2: Vector[String] = Vector(1, 2, 3, boom!)
返回结果res2正确记录了出错地点,而且所有清理过程都得到运行。当然,我们可以不用动Stream元素类型,用attempt:
val s6 = s3.attempt.onError {e => Stream.emit(e.getMessage)}
.onFinalize(Task.delay{println("finally end!")})
s6.runLog.unsafeRun //> normal end
//| finally end!
//| res3: Vector[Object] = Vector(Right(1), Right(2), Right(3), Left(java.lang.Exception: boom!))
我们在前面FunDA(1)里讨论过运算slick Query Action run返回结果类型是Future[Iterable[ROW]]。Slick获取数据的方式是一次性读入内存,所以本期标题提到的Static-Source就是指这样的一个内存中的集合。那么我们就可以不必考虑开启并占用数据库表这项操作了。我们只需要用FunDA DataRowType.getTypedRow函数获取了Iterable[ROW]结果后直接传给bracket就行了。现在最重要的是如何把Seq[ROW]转换成Stream[F[_],ROW]。我们可以用Seq的fold函数来构建Stream:
val data = Seq(,,,) //> data : Seq[Int] = List(1, 2, 3, 4)
val s8 = data.foldLeft(Stream[Task,Int]())((s,a) => s ++ Stream.emit(a))
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] s8.through(log("")).run.unsafeRun //> > 1
//| > 2
//| > 3
//| > 4
表面上看好像没什么问题,但仔细分析:Seq[ROW]可以是个超大的集合,而foldLeft是个递归函数,无论是否尾递归都有可能造成堆栈溢出错误(StackOverflowError)。看来还是用freemonad,它可以把每步运算都存放在内存结构里,可以在固定的堆栈空间运算。下面的函数用fs2.Pull类型结构可以把Seq[ROW]转换成Stream[F[_],ROW]:
def pullSeq[ROW](h: Seq[ROW]): Pull[Task, ROW, Unit] = {
val it = h.iterator
def go(it: Iterator[ROW]): Pull[Task, ROW, Unit] = for {
res <- Pull.eval(Task.delay({ if (it.hasNext) Some(it.next()) else None }))
next <- res.fold[Pull[Task, ROW, Unit]](Pull.done)(o => Pull.output1(o) >> go(it))
} yield next
go(it)
}
def streamSeq[ROW](h: Seq[ROW]): Stream[Task, ROW] =
pullSeq(h).close
虽然go是个递归函数,但因为Pull是个freemonad,每个flapMap循环(>>)会把新的Iterable it状态存放在heap内存里。由于每个步骤都是存放在内存结构里的,而运算这些步骤的模式是靠下游拖动逐步运算的,也就是说按下游拖动每次产生一个元素。pullSeq返回Pull,Pull.close >>> Stream,这就是streamSeq函数的作用了。现在我们可以直接用bracket来安全构建Stream:
val s9 = Stream.bracket(Task.delay(data))(streamSeq, _ => Task.delay())
s9.through(log("")).run.unsafeRun //> > 1
//| > 2
//| > 3
//| > 4
现在可以放心了。但我们的目的是为大众编程人员提供一个最低门槛的工具库,他们不需要了解Task, onError,onFinalize。。。我们必须把bracket函数使用方式搞得更直白点,让用户可以更容易调用:
type FDAStream[A] = Stream[Task,A]
implicit val strategy = Strategy.fromFixedDaemonPool()
//> strategy : fs2.Strategy = Strategy def fda_staticSource[ROW](acquirer: => Seq[ROW],
releaser: => Unit = (),
errhandler: Throwable => FDAStream[ROW] = null,
finalizer: => Unit = ()): FDAStream[ROW] = {
val s = Stream.bracket(Task(acquirer))(r => streamSeq(r), r => Task(releaser))
if (errhandler != null)
s.onError(errhandler).onFinalize(Task.delay(finalizer))
else
s.onFinalize(Task.delay(finalizer))
} //> fda_staticSource: [ROW](acquirer: => Seq[ROW], releaser: => Unit, errhandle
//| r: Throwable => demo.ws.streams.FDAStream[ROW], finalizer: => Unit)demo.ws.
//| streams.FDAStream[ROW]
如果完整调用fda_staticSource可以如下这样:
val s10 = fda_staticSource(data,
println("endofuse"), e => { println(e.getMessage);Stream.emit(-) },
println("finallyend"))
s10.through(log("")).run.unsafeRun //> > 1
//| > 2
//| > 3
//| > 4
//| endofuse
//| finallyend
最简单直接的方式如下:
val s11 = fda_staticSource(acquirer = data)
s11.through(log("")).run.unsafeRun //> > 1
//| > 2
//| > 3
//| > 4
又或者带异常处理过程的调用方法:
val s12 = fda_staticSource(acquirer = data, errhandler = {e => println(e.getMessage);Stream()})
s12.through(log("")).run.unsafeRun //> > 1
//| > 2
//| > 3
//| > 4
下面是这次讨论示范的源代码:
import fs2._
object streams {
val s = Stream.bracket(Task.delay(throw new Exception("Oh no!")))(
_ => Stream(,,) ++ Stream.fail(new Exception("boom!")) ++ Stream(,),
_ => Task.delay(println("normal end")))
//s.runLog.unsafeRun
val s1 = s.map(_.toString).onError {e => Stream.emit(e.getMessage)}
s1.runLog.unsafeRun
val s5 = s1.onFinalize(Task.delay{println("finally end!")})
s5.runLog.unsafeRun val s3 = Stream.bracket(Task.delay())(
_ => Stream(,,) ++ Stream.fail(new Exception("boom!")) ++ Stream(,),
_ => Task.delay(println("normal end")))
val s4 = s3.map(_.toString).onError {e => Stream.emit(e.getMessage)}
.onFinalize(Task.delay{println("finally end!")})
s4.runLog.unsafeRun
val s6 = s3.attempt.onError {e => Stream.emit(e.getMessage)}
.onFinalize(Task.delay{println("finally end!")}) s6.runLog.unsafeRun val data = Seq(,,,)
val s8 = data.foldLeft(Stream[Task,Int]())((s,a) => s ++ Stream.emit(a))
def log[A](prompt: String): Pipe[Task,A,A] =
_.evalMap {row => Task.delay{ println(s"$prompt> $row"); row }} s8.through(log("")).run.unsafeRun def pullSeq[ROW](h: Seq[ROW]): Pull[Task, ROW, Unit] = {
val it = h.iterator
def go(it: Iterator[ROW]): Pull[Task, ROW, Unit] = for {
res <- Pull.eval(Task.delay({ if (it.hasNext) Some(it.next()) else None }))
next <- res.fold[Pull[Task, ROW, Unit]](Pull.done)(o => Pull.output1(o) >> go(it))
} yield next
go(it)
}
def streamSeq[ROW](h: Seq[ROW]): Stream[Task, ROW] =
pullSeq(h).close
val s9 = Stream.bracket(Task.delay(data))(streamSeq, _ => Task.delay())
s9.through(log("")).run.unsafeRun type FDAStream[A] = Stream[Task,A]
implicit val strategy = Strategy.fromFixedDaemonPool() def fda_staticSource[ROW](acquirer: => Seq[ROW],
releaser: => Unit = (),
errhandler: Throwable => FDAStream[ROW] = null,
finalizer: => Unit = ()): FDAStream[ROW] = {
val s = Stream.bracket(Task(acquirer))(r => streamSeq(r), r => Task(releaser))
if (errhandler != null)
s.onError(errhandler).onFinalize(Task.delay(finalizer))
else
s.onFinalize(Task.delay(finalizer))
}
val s10 = fda_staticSource(data,
println("endofuse"), e => { println(e.getMessage);Stream.emit(-) },
println("finallyend"))
s10.through(log("")).run.unsafeRun
val s11 = fda_staticSource(acquirer = data)
s11.through(log("")).run.unsafeRun
val s12 = fda_staticSource(acquirer = data, errhandler = {e => println(e.getMessage);Stream()})
s12.through(log("")).run.unsafeRun }
FunDA(8)- Static Source:保证资源使用安全 - Resource Safety的更多相关文章
- Scalaz(51)- scalaz-stream: 资源使用安全-Resource Safety
scalaz-stream是一个数据流处理工具库,对资源使用,包括:开启文件.连接网络.连接数据库等这些公共资源使用方面都必须确定使用过程的安全:要保证在作业终止时能进行事后处理程序(finalize ...
- Spring源码分析——资源访问利器Resource之实现类分析
今天来分析Spring的资源接口Resource的各个实现类.关于它的接口和抽象类,参见上一篇博文——Spring源码分析——资源访问利器Resource之接口和抽象类分析 一.文件系统资源 File ...
- Spring源码分析——资源访问利器Resource之接口和抽象类分析
从今天开始,一步步走上源码分析的路.刚开始肯定要从简单着手.我们先从Java发展史上最强大的框架——Spring...旗下的资源抽象接口Resource开始吧. 我看了好多分析Spring源码的,每每 ...
- 攻城狮在路上(贰) Spring(三)--- Spring 资源访问利器Resource接口
Spring为了更好的满足各种底层资源的访问需求.设计了一个Resource接口,提供了更强的访问底层资源的能力.Spring框架使用Resource装载各种资源,包括配置文件资源.国际化属性文件资源 ...
- Spring资源访问接口Resource
该接口拥有对不同资源类型的实现类 boolean exists() 资源是否存在 boolean isOpen() 资源是否打开 URL getURL() 如果底层资源可以表示成URL,则该方法返回对 ...
- robot framework学习笔记之一 资源文件(Resource)和外部资源(External Resources)
一.资源文件(Resource) 测试套件主要是存放测试案例,资源文件主要是用来存放用户关键字. 添加资源 在目录型的Project/Test Suite下单击鼠标右键,选择『New Resou ...
- HTML 统一资源定位器(Uniform Resource Locators)
HTML 统一资源定位器(Uniform Resource Locators) URL 是一个网页地址.高佣联盟 www.cgewang.com URL可以由字母组成,如"runoob.co ...
- WPF如何设置Image.Source为资源图片
img.Source = new BitmapImage(new Uri(path,UriKind.RelativeOrAbsolute));
- source insight资源
http://www.cnblogs.com/Red_angelX/p/3713935.html https://github.com/redxu/sihook
随机推荐
- Loadrunner12.5-同一个网址通过vugen不能打开,但是直接在ie11中就可以打开
一:录制选项修改成“WinINet级别数据”,重新录制就可以成功打开网页了. 注:运行时设置--首选项--高级--“使用WinINet回放而非套接字(仅限Windows)”需要勾选上:否则录制脚本结束 ...
- Codeforces 658A. Robbers' watch 模拟
A. Robbers' watch time limit per test: 2 seconds memory limit per test: 256 megabytes input: standar ...
- Debian use sudo
刚安装好的Debian默认还没有sudo功能.1.安装sudo# apt-get install sudo2.编辑 /etc/sudoers ,添加如下行# visudoroot ALL=(ALL:A ...
- python的介绍和及基本的使用
一 什么是计算机 1 计算机就是由一堆硬件组成的一个机器. 2 硬件的分类: CPU:犹如人类的大脑,运行着需要运行的程序. 内存:将 CPU要运行的内容从硬盘中读取出来,然后CPU在内存里拿内容,只 ...
- 2018.09.10 bzoj1597: [Usaco2008 Mar]土地购买(斜率优化dp)
传送门 终究还是通宵了啊... 这是一道简单的斜率优化dp. 先对所有土地排序,显然如果有严格小于的两块土地不用考虑小的一块. 于是剩下的土地有一条边单增,另外一条单减. 我们假设a[i]是单减的,b ...
- UVa 11039 Building designing (贪心+排序+模拟)
题意:给定n个非0绝对值不相同的数,让他们排成一列,符号交替但绝对值递增,求最长的序列长度. 析:我个去简单啊,也就是个水题.首先先把他们的绝对值按递增的顺序排序,然后呢,挨着扫一遍,只有符号不同才计 ...
- nvarchar,varchar 区别
char char是定长的,也就是当你输入的字符小于你指定的数目时,char(8),你输入的字符小于8时,它会再后面补空值.当你输入的字符大于指定的数时,它会截取超出的字符. nva ...
- day03(接口,多态)
接口: 概念:是功能的集合,可以当做引用数据类型的一种.比抽象类更加抽象. 接口的成员: 成员变量:必须使用final修饰 默认被 public &a ...
- 集合(一)ArrayList
前言 这个分类中,将会写写Java中的集合.集合是Java中非常重要而且基础的内容,因为任何数据必不可少的就是该数据是如何存储的,集合的作用就是以一定的方式组织.存储数据.这里写的集合,一部分是比较常 ...
- 短URL
短网址应用已经在全国各大微博上开始流行了起来.例如QQ微博的url.cn,新郎的sinaurl.cn等. 我们在QQ微博上发布网址的时候,微博会自动判别网址,并将其转换,例如:http://url.c ...