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

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

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

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

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

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

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

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

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

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

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

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

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

  1. s1.attempt.runLog.unsafeRun //> acquiring:1
  2. //| releasing:0
  3. //| 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在中间环节里占用资源的例子:

  1. def logBracket[A]: A => Stream[Task,A] = a => {
  2. Stream.bracket(Task.delay { println(s"acquiring $a"); a })(
  3. _ => Stream.emit(a),
  4. _ => Task.delay { println(s"releasing $a") })
  5. } //> logBracket: [A]=> A => fs2.Stream[fs2.Task,A]
  6. Stream().flatMap(logBracket).map{ n =>
  7. if (n>) sys.error("oh no...") else n }.run.unsafeAttemptRun
  8. //> acquiring 3
  9. //| releasing 3
  10. //| 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后就立刻终止

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

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

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

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

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

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

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

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

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

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

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

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

  1. 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. angularjs自动化测试系列之jasmine

    angularjs自动化测试系列之jasmine jasmine参考 html <!DOCTYPE html> <html lang="en"> <h ...

  2. [转]深入理解JavaScript的变量作用域

    1.JavaScript的作用域链 2.函数体内部,局部变量的优先级比同名的全局变量高. 3.JavaScript没有块级作用域. 4.函数中声明的变量在整个函数中都有定义. 5.未使用var关键字定 ...

  3. Sql Server系列:排序函数

    在SQL Server中有4个排序函数:ROW_NUMBER().RANK().DENSE_RANK()及NTILE()函数. 1. ROW_NUMBER()函数 ROW_NUMBER()函数为每条记 ...

  4. SQL Server中使用Check约束提升性能

        在SQL Server中,SQL语句的执行是依赖查询优化器生成的执行计划,而执行计划的好坏直接关乎执行性能.     在查询优化器生成执行计划过程中,需要参考元数据来尽可能生成高效的执行计划, ...

  5. js立即执行函数

    一.JS立即执行函数的写法 方式1.最前最后加括号 (function(){alert(1);}()); 方式2.function外面加括号   (function(){alert(1);})(); ...

  6. Oracle_SQL函数-分组函数

    分组函数 什么是分组函数 分组函数作用于一组数据,并对一组数据返回一个值 组函数类型:主要有6种 AVG - 平均 COUNT - 计数 MAX - 最大 MIN - 最小 SUM - 求和 STDD ...

  7. YII 的源码分析(-)

    做为源码分析的首秀,我就挑了yii(读作歪依依而不是歪爱爱):它的赞美之词我就不多说了,直接入正题.先准备材料,建议直从官网下载yii的源码包(1.1.15). 在demos里边有一个最简单的应用—h ...

  8. Web接口测试工具--Jmeter

    关于Jmeter性能测试工具不再过多介绍.如果你要学习软件性能测试,那么多少应该会对它有所耳闻. 强烈建议阅读官方文档学习:http://jmeter.apache.org/index.html 还有 ...

  9. 关于Java中的transient关键字

    Java中的transient关键字是在序列化时候用的,如果用transient修饰变量,那么该变量不会被序列化. 下面的例子中创建了一个Student类,有三个成员变量:id,name,age.ag ...

  10. 如何用easyui+JAVA 实现动态拼凑datagrid表格(续)

    前面一段时间写了一篇文章: 如何用easyui+JAVA 实现动态拼凑datagrid表格 这篇文章的话,效果是可以实现,但是经过我反复试验,还是存在一些问题的. 今天这篇文章就是向大家介绍下如何避免 ...