Kotlin协程之Flow异常示例处理
作者:LeeDuo. 发布时间:2022-10-02 09:30:58
示例
代码如下:
launch(Dispatchers.Main) {
// 第一部分
flow {
emit(1)
throw NullPointerException("e")
}.catch {
Log.d("liduo", "onCreate1: $it")
}.collect {
Log.d("liudo", "onCreate2: $it")
}
// 第二部分
flow {
emit(1)
}.onCompletion {
Log.d("liduo", "onCreate3: $it")
}.collect {
Log.d("liudo", "onCreate4: $it")
}
// 第三部分
flow {
emit(1)
throw NullPointerException("e")
}.retryWhen { cause, attempt ->
cause !is NullPointerException && attempt <= 2
}.collect {
Log.d("liudo", "onCreate5: $it")
}
}
一.catch方法
catch方法用于捕获上游流产生的异常,代码如下:
public fun <T> Flow<T>.catch(action: suspend FlowCollector<T>.(cause: Throwable) -> Unit): Flow<T> =
flow { // 创建Flow对象
// 触发上游流的执行,并捕获异常
val exception = catchImpl(this)
// 捕获到异常,则回调action处理
if (exception != null) action(exception)
}
catch方法是Flow接口的扩展方法,并返回一个Flow类型的对象。在catch方法中,调用flow方法创建了一个Flow对象。
catch方法核心是通过catchImpl方法实现异常的捕获,如果成功捕获到异常,则回调参数action处理。这里参数action是FlowCollector接口的扩展方法,因此可以继续调用emit方法,向下游发送值。
catchImpl方法
当下游调用collect方法时,会触发catch方法创建的Flow对象的执行,并调用catchImpl方法来处理,代码如下:
internal suspend fun <T> Flow<T>.catchImpl(
collector: FlowCollector<T>
): Throwable? {
// 保存下游流执行抛出的异常
var fromDownstream: Throwable? = null
try {
// 触发上游流的执行
collect {
try {
// 将上游流发送的值作为参数,触发下游流执行
collector.emit(it)
} catch (e: Throwable) { // 如果下游流在执行中发生异常,保存并抛出
fromDownstream = e
throw e
}
}
} catch (e: Throwable) { // 这里捕获的异常,可能为上游流的异常——collect方法,
// 也可能为下游流的异常——emit方法
// 如果异常是下游流产生的异常,或者是协程取消时抛出的异常
if (e.isSameExceptionAs(fromDownstream) || e.isCancellationCause(coroutineContext)) {
throw e // 再次抛出,交给下游处理
} else { // 如果是上游流的异常且不为协程取消异常
return e // 成功捕获
}
}
// 未捕获到异常,返回
return null
}
catchImpl方法是Flow接口的扩展方法,因此在调用collect方法时,会触发上游流的执行。catchImpl方法的核心在于:将上游发出的值传递给下游处理,并对这一过程进行了异常捕获操作。
二. onCompletion方法
onCompletion方法用于在上游的流全部执行完毕后最后执行,代码如下:
public fun <T> Flow<T>.onCompletion(
action: suspend FlowCollector<T>.(cause: Throwable?) -> Unit
): Flow<T> = unsafeFlow { // 创建一个Flow对象
try {
// 触发上游流的执行
// this表示下游的FlowCollector
collect(this)
} catch (e: Throwable) {// 如果下游发生异常
// 将异常封装成ThrowingCollector类型的FlowCollector,并回调参数action,
ThrowingCollector(e).invokeSafely(action, e)
// 抛出异常
throw e
}
// 如果正常执行结束,会走到这里
val sc = SafeCollector(this, currentCoroutineContext())
try {
// 回调执行参数action
sc.action(null)
} finally {
sc.releaseIntercepted()
}
}
onCompletion方法是Flow接口的扩展方法,因此在调用collect方法时,会触发上游流的执行。同时,传入this作为参数,this表示下游流调用collect方法时,传给unsafeFlow方法创建的Flow对象的类型为FlowCollector的对象。onCompletion方法的核心在于:将自身创建的Flow对象作为上游与下游的连接容器,只有当流全部执行完毕或执行过程中发生异常,collect方法才可以执行完成,继续向下执行。
1.unsafeFlow方法
unsafeFlow方法用于创建一个类型为Flow对象,与之前在Kotlin协程:Flow基础原理提到过的SafeFlow类相比,unsafeFlow方法创建的Flow对象不会对执行的上下文进行检查,代码如下:
@PublishedApi
internal inline fun <T> unsafeFlow(@BuilderInference crossinline block: suspend FlowCollector<T>.() -> Unit): Flow<T> {
// 返回一个匿名内部类
return object : Flow<T> {
// 回调collect方法是直接执行block
override suspend fun collect(collector: FlowCollector<T>) {
collector.block()
}
}
}
虽然onCompletion方法内部使用unsafeFlow方法创建Flow对象,但却使用了SafeCollector类。根据之前在Kotlin协程:Flow基础原理提到的,调用SafeCollector类的emit方法时,会对上下文进行检查。因此实际效果与使用SafeFlow类效果相同。
2.ThrowingCollector类
ThrowingCollector类也是一种FlowCollector,用于包裹异常。当调用它的emit方法时,会抛出包裹的异常,代码如下:
private class ThrowingCollector(private val e: Throwable) : FlowCollector<Any?> {
override suspend fun emit(value: Any?) {
// 抛出异常
throw e
}
}
为什么要重新创建ThrowingCollector对象,而不使用下游的FlowCollector对象呢?
为了防止当下游的流执行失败时,onCompletion方法的action参数执行时调用emit方法发送数据,这样会导致onCompletion方法作为在“finially代码块”使用时不是最后执行的方法。
onCompletion方法搭配与catch方法,实现try-catch-finially代码块的效果。
三. retryWhen方法
retryWhen方法与catch方法类似,都可以用于捕获上游流产生的异常。但两者不同之处在于,retryWhen方法还可以根据“异常类型”和“重试次数”来决定是否要再次触发上游流的执行,而且当retryWhen方法不打算再次触发上游流的执行时,捕获的异常会被抛出,代码如下:
// 参数cause表示捕获到的异常
// 参数attempt表示重试的次数
// 参数predicate返回true表示重新触发上游流的执行
public fun <T> Flow<T>.retryWhen(predicate: suspend FlowCollector<T>.(cause: Throwable, attempt: Long) -> Boolean): Flow<T> =
// 创建一个Flow对象
flow {
// 记录重试次数
var attempt = 0L
// 表示是否重新触发
var shallRetry: Boolean
do {
// 复位成false
shallRetry = false
// 触发上游流的执行,并捕获异常
val cause = catchImpl(this)
// 如果捕获到异常
if (cause != null) {
// 用户判断,是否要重新触发
if (predicate(cause, attempt)) {
// 表示要重新触发
shallRetry = true
// 重试次数加1
attempt++
} else { // 如果用户不需要重新触发
// 则抛出异常
throw cause
}
}
// 判断是否重新触发
} while (shallRetry)
}
retryWhen方法是Flow接口的扩展方法。retryWhen方法的核心通过catchImpl方法实现对上游流的触发及异常捕获,并加入了由用户判断的重试逻辑实现。
来源:https://blog.csdn.net/LeeDuoZuiShuai/article/details/126828155
猜你喜欢
- 本文实例为大家分享了Java实现多线程在线聊天的具体代码,供大家参考,具体内容如下上一篇博客通过UDP实现了聊天,但只能单方面发送消息,这次
- 官网文档背景项目A中需要多数据源的实现,比如UserDao.getAllUserList() 需要从readonly库中读取,但是UserD
- 一、代理的概念 * 技术是整个java技术中最重要的一个技术,它是学习java框架的基础,不会 * 技术,那么在学习Spring这些框架
- 这篇文章主要介绍了SpringBoot以war包形式部署到外部Tomcat过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有
- 基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void通
- 前言前不久遇到一个问题,是公司早期的基础库遇到的,其实很低级,但是还是记录下来。出错点是一个 IO 流的写入bug,我们项目会有一种专有的数
- 本文实例为大家分享了java实现仿射密码加密解密的具体代码,供大家参考,具体内容如下加密:将明文转化为对应的数字,如 ‘a'->
- using System;using System.Collections.Generic;using System.ComponentMo
- 本文实例讲述了Spring实战之@Autowire注解用法。分享给大家供大家参考,具体如下:一 配置<?xml version=&qu
- 协同过滤简单来说是利用某兴趣相投、拥有共同经验之群体的喜好来推荐用户感兴趣的信息,个人通过合作的机制给予信息相当程度的回应(如评分)并记录下
- 定义:结点的带权路径长度为从该结点到树根之间的路径长度与结点上权的乘积。树的带权路径长度为树中所有叶子结点的带权路径长度之和。假设有n个权值
- 前言众所周知Java提供File类,让我们对文件进行操作,下面就来简单整理了一下File类的用法。 话不多说了,来一起看看详细的介绍吧1.基
- Springboot添加server.servlet.context-pathserver.servlet.context-path配置的作
- 本文实例分析了Android多线程。分享给大家供大家参考,具体如下:在Android下面也有多线程的概念,在C/C++中,子线程可以是一个函
- 创建一个类,在该类的主方法中创建标准输入流的扫描器对象,提示用户输入一个整数,并通过扫描器的方法来接受这个整数,然后通过三元运算符判断该数字
- 一、需求对于Java开发工程师来说,可能手头上同时负责不同的项目,但是由于历史的原因,Java版本可能没有做到统一升级,有的项目是使用JDK
- 在使用Java集合的时候,都需要使用Iterator。但是java集合中还有一个迭代器ListIterator,在使用List、ArrayL
- 前言单例模式,是工作中比较常见的一种设计模式,通常有两种实现方式,懒汉式和饿汉式。但是这两种实现方式存在一些问题。懒汉式需要在多线程环境下使
- 前言前段时间在写RPC框架的时候用到了Kryo、Hessian、Protostuff三种序列化方式。但是当时因为急于实现功能,就只是简单的的
- 降低springcloud版本,改成Hoxton.SR5就好了,再次改成Hoxton.SR12,也不报错了,很奇怪。也发现gateway版本