fs2在处理异常及资源使用安全方面也有比较大的改善。fs2 Stream可以有几种方式自行引发异常:直接以函数式方式用fail来引发异常、在纯代码里隐式引发异常或者在运算中引发异常,举例如下:

 /函数式
val err = Stream(,,) ++ Stream.fail(new Exception("oh,no..."))
//> err : fs2.Stream[Nothing,Int] = append(Segment(Emit(Chunk(1, 2, 3))), Segment(Emit(Chunk(()))).flatMap(<function1>))
//隐式转换
val err1 = Stream(,,) ++ (throw new Exception("oh my god!"))
 //> err1 : fs2.Stream[Nothing,Int] = append(Segment(Emit(Chunk(1, 2, 3))), Segment(Emit(Chunk(()))).flatMap(<function1>))
//运算中
val err2 = Stream.eval(Task.delay { throw new Exception("it suck!")})
//> err2 : fs2.Stream[fs2.Task,Nothing] = attemptEval(Task).flatMap(<function1>)

我们可以用非函数式方式处理异常:

 try err.toList catch {case e => println(e.getMessage)}
//> oh,no...
//| res0: Any = ()
try err2.run.unsafeRun catch {case e => println(e.getMessage)}
//> it suck!

当然,我们会选择用纯代码方式处理异常:

 err.map(_.toString).onError { case e => Stream.emit(e.getMessage) }.toList
 //> res1: List[String] = List(1, 2, 3, oh,no...)
err1.map(_.toString).onError { case e => Stream.emit(e.getMessage) }.toList
//> res2: List[String] = List(1, 2, 3, oh my god!)
err2.onError { case e => Stream.emit(e.getMessage) }.runLog.unsafeRun
//> res3: Vector[String] = Vector(it suck!)

我们在上一篇讨论中介绍过fs2提供了一个bracket函数来保证资源的安全使用。bracket函数款式是这样的:

def bracket[F[_],R,A](r: F[R])(use: R => Stream[F,A], release: R => F[Unit]) = Stream.mk {
StreamCore.acquire(r, release andThen (Free.eval)) flatMap (use andThen (_.get))
}

r代表一个获取资源R的运算,use是对资源R的使用操作,release是事后对R的处理。安全使用资源就是无论use是正常完成或者异常中断,release都会保证得到运行。我们看看下面的例子:

 val counter = new java.util.concurrent.atomic.AtomicLong()
//> counter : java.util.concurrent.atomic.AtomicLong = 0
val acquire = Task.delay { println(s"acquiring:${counter.incrementAndGet}") }
//> acquire : fs2.Task[Unit] = Task
val release = Task.delay { println(s"releasing:${counter.decrementAndGet}") }
//> release : fs2.Task[Unit] = Task
Stream.bracket(acquire)(_ => Stream(,,) ++ err, _ => release).run.unsafeRun
//> acquiring:1
//| releasing:0
//| java.lang.Exception: oh,no...
//| at fs2Safety$$anonfun$main$1$$anonfun$3.apply(fs2Safety.scala:4)
//| at fs2Safety$$anonfun$main$1$$anonfun$3.apply(fs2Safety.scala:4)

在上面的例子里use会引发异常中断,但release还是得到运行。我们可以用onError来把错误信息截住:

  s1.map(_.toString).onError {case e => Stream.emit(e.getMessage)}.runLog.unsafeRun
//> acquiring:1
//| releasing:0
//| res4: Vector[String] = Vector(4, 5, 6, 1, 2, 3, oh,no...)

我们也可以用attempt来获取所有运算结果:

 s1.attempt.runLog.unsafeRun                       //> acquiring:1
//| releasing:0
//| res5: Vector[fs2.util.Attempt[Int]] = Vector(Right(4), Right(5), Right(6), Right(1), Right(2), Right(3), Left(java.lang.Exception: oh,no...))

我们再举个在bracket在中间环节里占用资源的例子:

  def logBracket[A]: A => Stream[Task,A] = a => {
Stream.bracket(Task.delay { println(s"acquiring $a"); a })(
_ => Stream.emit(a),
_ => Task.delay { println(s"releasing $a") })
} //> logBracket: [A]=> A => fs2.Stream[fs2.Task,A]
Stream().flatMap(logBracket).map{ n =>
if (n>) sys.error("oh no...") else n }.run.unsafeAttemptRun
//> acquiring 3
//| releasing 3
//| res6: fs2.util.Attempt[Unit] = Left(java.lang.RuntimeException: oh no...)

应该注意到:在任何情况下releasing都会运行。

实际上所谓安全的资源使用(resource safety)主要是指在任何形式的运算终止情况下运算的事后处理程序都能保证得到运行。运算的终止形式有以下几种:

1、正常终止。如Stream(1,2,3)的运算:在发出一节Chunk(1,2,3)后终止

2、异常终止。在运算过程中发生异常中途终止

3、强迫终止。用户强制终止,如:Stream.range(1,5).take(1),在发出元素1后就立刻终止

我们要注意的是第三种情况。先看个例子:

 val s5 = (Stream() ++ Stream.fail(new Exception("oh no...")))
//> s5 : fs2.Stream[Nothing,Int] = append(Segment(Emit(Chunk(1))), Segment(Emit(Chunk(()))).flatMap(<function1>))
s5.map(_.toString).onError {case e => Stream.emit(e.getMessage)}.toList
//> res7: List[String] = List(1, oh no...)
s5.take().toList //> res8: List[Int] = List(1)
(Stream("a") ++ Stream("bc")).onComplete(Stream.emit("completed!")).toList
//> res9: List[String] = List(a, bc, completed!)
s5.map(_.toString).onComplete(Stream.emit("completed!")).take().toList
//> res10: List[String] = List(1)
s5.covary[Task].map(_.toString).onFinalize(Task.delay { println("finalized!")})
.take().runLog.unsafeRun //> finalized!
//| res11: Vector[String] = Vector(1)

我们看到:虽然s5会引发异常,可以用onError来捕获异常。但奇怪的是用take(1)后不会发生异常。这是因为take(1)是用户强制终止操作,即在发出一个元素后即刻终止。此时还没开始处理fail。值得注意的是运算遭到强制终止后onComplete是不会运行的,onFinalize则在任何情况下都能得到运行。
说到运算安全,FP的运行方式以递归算法为主:flatMap就是一个递归算法,那么在fs2里能不能保证运算的安全呢?下面的测试程序可以成为最具代表性的示范:

 // Sanity tests - not run as part of unit tests, but these should run forever
// at constant memory.
//
object ResourceTrackerSanityTest extends App {
val big = Stream.constant().flatMap { n =>
Stream.bracket(Task.delay(()))(_ => Stream.emits(List(, , )), _ => Task.delay(()))
}
big.run.unsafeRun
} object RepeatPullSanityTest extends App {
def id[A]: Pipe[Pure, A, A] = _ repeatPull Pull.receive1 { case h #: t => Pull.output1(h) as t }
Stream.constant().covary[Task].throughPure(id).run.unsafeRun
}

运行以上两个程序都不会产生StackOverflowError错误。

从上面的讨论里我们知道了bracket函数是fs2建议的安全运算机制。我们可以用bracket来读取我们自定义的资源,如:数据库或者一些外设,这样我们可以确定当运算终止后事后处理机制一定会发生作用。fs2在io.file对象里提供了自身的文件读写功能,这些函数都具备了资源使用安全机制。也就是说当对fs2.file的使用终止后,事后处理机制运行是得到保证的。下面我们分享一个fs2.file的经典例子:

 def fahrenheitToCelsius(f: Double): Double =
(f - 32.0) * (5.0/9.0) //> fahrenheitToCelsius: (f: Double)Double val converter: Task[Unit] =
io.file.readAll[Task](java.nio.file.Paths.get("/users/tiger-macpro/fahrenheit.txt"), )
.through(text.utf8Decode)
.through(text.lines)
.filter(s => !s.trim.isEmpty && !s.startsWith("//"))
.map(line => fahrenheitToCelsius(line.toDouble).toString)
.intersperse("\n")
.through(text.utf8Encode)
.through(io.file.writeAll(java.nio.file.Paths.get("/users/tiger-macpro/celsius.txt")))
.run //> converter : fs2.Task[Unit] = Task
converter.unsafeRun

首先在这个例子里可以肯定所有使用的文件(fahrenheit.txt, celsius.txt)在任何情况下都会得到释放。readAll的函数款式是这样的:

def readAll[F[_]](path: Path, chunkSize: Int)(implicit F: Effect[F]): Stream[F, Byte] = {...}

值得注意的是readAll是按批次逐批从文件里读入的,这样可以避免一次性把所有内容全部搬到内存里。上面的例子是按4K字节读取的。readAll返回结果类型是Byte,我们要用个transducer把Byte转成String,这些转换函数可以在text对象里发现。text.utf8Decode的函数类型如下:

/** Converts UTF-8 encoded byte stream to a stream of `String`. */
def utf8Decode[F[_]]: Pipe[F, Byte, String] =
_.chunks.through(utf8DecodeC)

utf8Decode是个Pipe:从Byte转到String。同样如果从String转成Byte的话可以用utf8Encode。当我们需要把String写入文件时就需要utf8Encode来转换Byte了。writeAll的函数款式如下:

def writeAll[F[_]](path: Path, flags: Seq[StandardOpenOption] = List(StandardOpenOption.CREATE))(implicit F: Effect[F]): Sink[F, Byte] = {...}

writeAll的结果类型是Sink[F,Byte],代表输入是Stream[F,Byte],所以我们必须用utf8Encode先把String转成Byte。text.lines是fs2自带的文字型iterator:在fs2里不再使用java的iterator了。另外interperse的作用是在元素由String转换成Byte之前先进行分行。在这篇讨论里我们主要介绍的是pipe对象中的函数。我们将会在下次关于多线程运算的讨论里介绍pipe2。

Scalaz(56)- scalaz-stream: fs2-安全运算,fs2 resource safety的更多相关文章

  1. Scalaz(51)- scalaz-stream: 资源使用安全-Resource Safety

    scalaz-stream是一个数据流处理工具库,对资源使用,包括:开启文件.连接网络.连接数据库等这些公共资源使用方面都必须确定使用过程的安全:要保证在作业终止时能进行事后处理程序(finalize ...

  2. Akka(20): Stream:异步运算,压力缓冲-Async, batching backpressure and buffering

    akka-stream原则上是一种推式(push-model)的数据流.push-model和pull-model的区别在于它们解决问题倾向性:push模式面向高效的数据流下游(fast-downst ...

  3. Scalaz(55)- scalaz-stream: fs2-基础介绍,fs2 stream transformation

    fs2是scalaz-stream的最新版本,沿用了scalaz-stream被动式(pull model)数据流原理但采用了全新的实现方法.fs2比较scalaz-stream而言具备了:更精简的基 ...

  4. Scalaz(58)- scalaz-stream: fs2-并行运算示范,fs2 parallel processing

    从表面上来看,Stream代表一连串无穷数据元素.一连串的意思是元素有固定的排列顺序,所以对元素的运算也必须按照顺序来:完成了前面的运算再跟着进行下一个元素的运算.这样来看,Stream应该不是很好的 ...

  5. Scalaz(59)- scalaz-stream: fs2-程序并行运算,fs2 running effects in parallel

    scalaz-stream-fs2是一种函数式的数据流编程工具.fs2的类型款式是:Stream[F[_],O],F[_]代表一种运算模式,O代表Stream数据元素的类型.实际上F就是一种延迟运算机 ...

  6. Scalaz(57)- scalaz-stream: fs2-多线程编程,fs2 concurrency

    fs2的多线程编程模式不但提供了无阻碍I/O(java nio)能力,更为并行运算提供了良好的编程工具.在进入并行运算讨论前我们先示范一下fs2 pipe2对象里的一些Stream合并功能.我们先设计 ...

  7. Scalaz(50)- scalaz-stream: 安全的无穷运算-running infinite stream freely

    scalaz-stream支持无穷数据流(infinite stream),这本身是它强大的功能之一,试想有多少系统需要通过无穷运算才能得以实现.这是因为外界的输入是不可预料的,对于系统本身就是无穷的 ...

  8. Scalaz(53)- scalaz-stream: 程序运算器-application scenario

    从上面多篇的讨论中我们了解到scalaz-stream代表一串连续无穷的数据或者程序.对这个数据流的处理过程就是一个状态机器(state machine)的状态转变过程.这种模式与我们通常遇到的程序流 ...

  9. Scalaz(47)- scalaz-stream: 深入了解-Source

    scalaz-stream库的主要设计目标是实现函数式的I/O编程(functional I/O).这样用户就能使用功能单一的基础I/O函数组合成为功能完整的I/O程序.还有一个目标就是保证资源的安全 ...

随机推荐

  1. CSS3 制作一个边框向周围散开的按钮效果

    我们将要达到的是如下的效果(若效果未出现请刷新): 分析 主要还是运用CSS3的transition, animation, transform还有渐变背景等特性. 由于按钮在鼠标进入时有不同的样式, ...

  2. Step by Step 创建一个新的Dynamics CRM Organization

    原创地址:http://www.cnblogs.com/jfzhu/p/4012833.html 转载请注明出处 前面演示过如何安装Dynamics CRM 2013,参见<Step by st ...

  3. [nodemon] Internal watch failed: watch ENOSPC错误解决办法

    运行环境:Ubuntu 16.04, WebStorm 2016.1.3, node.js v0.12.5, nodemon 1.9.2 在WS自带的终端输入nodemon server.js启动项目 ...

  4. JS实战 · 表格行颜色间隔显示,并在鼠标指定行上高亮显示

    思路: 1.获取所有行对象,将需要间隔颜色显示的行对象进行动态的className属性指定:      前提是:先定义好类选择器,就是说给行对象赋予name. 2.高亮用到两个事件:onmouseov ...

  5. 使用Emit把Datatable转换为对象集合(List<T>)

    Emit生成动态方法部分摘自网上,但是经过修改,加入了对委托的缓存以及类结构的调整,使之调用更简洁方便.大致的思路是:要实现转换datatable到某个指定对象的集合,本质是实现转换一个datarow ...

  6. Nginx内置变量

    $args #请求中的参数值 $query_string #同 $args $arg_NAME #GET请求中NAME的值 $is_args #如果请求中有参数,值为"?",否则为 ...

  7. Content-Type List

    Content-Type List Description of Data Content Typical Filename Extensions MIME type/subtype       Te ...

  8. react-native环境搭建

    目标平台 Android 开发平台 windows 开发环境安装建议:由于开发环境存在差异,建议参照react官网 或者react中文网 安装, react-native -- 在Windows下搭建 ...

  9. TSQL “匹配全部”语义的实现

    在TSQL中,有exists子句表示存在,表示匹配任意一行数据,但是,如何表示匹配全部的数据行.例如,表示一个学生选修了所有课程,这就是“匹配全部”语义的一个经典实例. 示例,获取“选修全部”课程的学 ...

  10. Join 和 apply 用法

    TSQL中的join语句共有五种类型,left join,right join,inner join,full join,cross join 为了描述方便,解释一个名词"保留表" ...