likes
comments
collection
share

重拾 Kotlin 协程——获取返回值(3)

作者站长头像
站长
· 阅读数 10

前言

当我们在同步代码块中执行代码时,获取返回值是一件十分轻松的事情,直接运算结果进行返回即可。但是,协程是一种异步的概念,所以需要一些特别的操作才能获取协程的返回值。经研究,一般使用以下三种:

  • async
  • suspendCoroutine
  • suspendCancellableCoroutine

async

相信大家对 async 都十分理解了,主要流程就是使用 async 去开启协程,然后调用 async 返回的 Deferred 的 await() 方法,即可获取 async 协程运算的结果。具体的代码示例如下:

    CoroutineScope(Dispatchers.Default).launch {
        val job = async {
            println("async 正在执行")
            return@async "返回值"
        }
        delay(1000)
        println("async 返回结果:${job.await()}")
    }

运行结果:

I/System.out: async 正在执行
I/System.out: async 返回结果:返回值

suspendCoroutine

resume

与 async 不同,suspendCoroutine 只是一个挂起函数,无法开启协程,所以,需要在其它协程作用域里面使用 suspendCoroutine。

至于 suspendCoroutine 的使用流程也比较简单,suspendCoroutine 返回的对象就是返回值,可以直接进行使用。具体的代码示例如下:

    CoroutineScope(Dispatchers.Default).launch {
        val result = suspendCoroutine<String> {
            println("suspendCoroutine 正在执行")
            it.resume("返回值")
        }
        println("suspendCoroutine 返回结果:$result")
    }

运行结果:

I/System.out: suspendCoroutine 正在执行
I/System.out: suspendCoroutine 返回结果:返回值

不过,这里有点要注意的是,在这个例子的 suspendCoroutine 中,我们是使用 resume 进行值的提交,但是,其实不仅只有 resume 才能提交值,还有其它方式:

  • resume
  • resumeWithException
  • resumeWith

下面,我们来一一解释。

resumeWithException

当我们在进行网络请求的时候,往往需要两种处理,一种是请求成功的情况,另外一种是请求失败的情况。

那 suspendCoroutine 如何去做到这一点?

答案是:抛出异常

重拾 Kotlin 协程——获取返回值(3)

说实说,我是有点惊呆了。不过 suspendCoroutine 提供了一直稍微优化一点的抛异常方式,那就是 resumeWithException。

话不多说,我们来看下示例:

        try{
            val result = suspendCoroutine<String> {
                println("suspendCoroutine 正在执行")
                it.resumeWithException(Exception("我是异常"))
            }
            println("suspendCoroutine 执行成功,返回结果:$result")
        }catch (e: java.lang.Exception){
            println("suspendCoroutine 执行失败,返回异常:$e")
        }

运行结果:

I/System.out: suspendCoroutine 正在执行
I/System.out: suspendCoroutine 执行失败,返回异常:java.lang.Exception: 我是异常

当然,也不是只有异常这种方式才能进行区分操作,其实,也可以对于 suspendCoroutine 的返回值进行封装,给它赋予不同的标识,然后进行区分处理。

另外,这种抛异常的思路和封装返回值的思路同样也适用于 async,这里就不过多举例说明。

resumeWith

首先,我们来看看 resumeWith 完整方法:

public fun resumeWith(result: Result<T>)

里面的参数是一个 Result 对象:

public value class Result<out T> @PublishedApi internal constructor(
    @PublishedApi
    internal val value: Any?
) : Serializable {

internal constructor,由此,我们无法直接通过构造方法来构造该对象,只能考虑调用 Result 里面的方法进行构造:

    public companion object {

        public inline fun <T> success(value: T): Result<T> =
            Result(value)


        public inline fun <T> failure(exception: Throwable): Result<T> =
            Result(createFailure(exception))
    }

其实就是:Result.success()Result.failure()

然后,我们再来看看 resume 和 resumeWithException 的源码:

public inline fun <T> Continuation<T>.resume(value: T): Unit =
    resumeWith(Result.success(value))

public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
    resumeWith(Result.failure(exception))

这下大家应该就知道如何使用 resumeWith 了。

其实,就是需要去不使用,因为 resume 和 resumeWithException 已经涵盖了 resumeWith 的全部情况。

suspendCancellableCoroutine

当我们使用 suspendCoroutine 时,若该协程已被 cancel(),调用 resume() 也是会正常返回值的。

    CoroutineScope(Dispatchers.Default).launch {
        val result = suspendCoroutine<String>{
            println("suspendCoroutine 正在执行")
            cancel()
            it.resume("返回值")
        }
        println("suspendCoroutine 执行成功,返回结果:$result")
    }

输出结果:

I/System.out: suspendCoroutine 正在执行
I/System.out: suspendCoroutine 执行成功,返回结果:返回值

但是,这并不是我们想要的,因为都已经 cancel() 了,说明就不希望再要该返回值了。为了处理这种情况,我们可以考虑使用 suspendCancellableCoroutine。

但是,有一点是需要我们特别注意的,在使用 suspendCancellableCoroutine 后,若已经 cancel() 后,再调用 resume() ,是会直接抛出异常的,我们需要注意异常的捕获。示例如下:

    CoroutineScope(Dispatchers.Default).launch {
        try{
            val result = suspendCancellableCoroutine<String>{
                println("suspendCancellableCoroutine 正在执行")
                cancel()
                it.resume("返回值")
            }
            println("suspendCancellableCoroutine 执行成功,返回结果:$result")
        }catch (e: java.lang.Exception){
            println("suspendCancellableCoroutine 执行失败,返回异常:$e")
        }
    }

输出结果:

I/System.out: suspendCancellableCoroutine 正在执行
I/System.out: suspendCancellableCoroutine 执行失败,返回异常:kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@e9b1ab

另外,若使用 suspendCancellableCoroutine 的话,其 resume() 方法还有另外一个重载方法:

public fun resume(value: T, onCancellation: ((cause: Throwable) -> Unit)?)

我们可以通过 onCancellation 进行一些快速操作:

    CoroutineScope(Dispatchers.Default).launch {
        try{
            val result = suspendCancellableCoroutine<String>{
                println("suspendCancellableCoroutine 正在执行")
                cancel()
                it.resume("返回值"){ cause->
                    println("suspendCancellableCoroutine 被取消了,cause:$cause")
                }
            }
            println("suspendCancellableCoroutine 执行成功,返回结果:$result")
        }catch (e: java.lang.Exception){
            println("suspendCancellableCoroutine 执行失败,返回异常:$e")
        }
    }

输出结果:

I/System.out: suspendCancellableCoroutine 正在执行
I/System.out: suspendCancellableCoroutine 被取消了,cause:kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@e9b1ab
I/System.out: suspendCancellableCoroutine 执行失败,返回异常:kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@e9b1ab

其它补充

当我们使用 suspendCoroutine 或者 suspendCancellableCoroutine 的时候,我们都知道使用 resume() 能够返回值,那问题来了,我们调用 resume() 之后,后续的代码会被执行吗?若多次调用 resume() 又会怎么样?

废话不多说,来个实验就行:

    CoroutineScope(Dispatchers.Default).launch {
        try{
            val result = suspendCancellableCoroutine<String>{
                println("suspendCancellableCoroutine 正在执行")
                it.resume("返回值")
                println("suspendCancellableCoroutine 已经返回")
                it.resume("返回值2")
                println("suspendCancellableCoroutine 再次返回")
            }
            println("suspendCancellableCoroutine 执行成功,返回结果:$result")
        }catch (e: java.lang.Exception){
            println("suspendCancellableCoroutine 执行失败,返回异常:$e")
        }
    }

输出结果:

I/System.out: suspendCancellableCoroutine 正在执行
I/System.out: suspendCancellableCoroutine 已经返回
I/System.out: suspendCancellableCoroutine 执行失败,返回异常:java.lang.IllegalStateException: Already resumed, but proposed with update 返回值2

由此,我们可以得出几个结论:

  • 调用 resume() 之后,后续代码还会继续执行。
  • 第二次调用 resume() 后,后续代码不会被执行,并且会抛出异常,这一点,suspendCoroutine 和 suspendCancellableCoroutine 都是一样的。
转载自:https://juejin.cn/post/7094891417868173348
评论
请登录