这是《Kotlin 协程基础课》的第4篇,也是最后一篇文章。
我不在意十年后的自己是什么样子,我在意的是,十年后的我怎么看现在的自己。
协程是强大的工具,通过前面三篇文章我们介绍了什么是协程、如何使用协程、协程里的CoroutineContext到底是个什么玩意儿。本文是《Kotlin协程基础课》系列的最后一篇文章,将从原理上进行简要的介绍。
从混合开发说起
对Java开发者而言,“回调”是再常见不过的概念了。从各种SDK到我们自己开发的代码,处处充满了回调。某个任务需要长时间执行,同时我们希望能在任务完成时得到通知,在函数参数里加上一个回调对象,用以收取结果,是十分常见的解决方案。
一个回调Demo
我们假设有个耗时计算任务,sleep指定时间然后返回数值,代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
fun calcSlowly(inp: Int, callback: CalcTaskCallback<Int>) { try { println("calcSlowly inp=$inp in thread: ${Thread.currentThread().name}") val result = inp * 1000L Thread.sleep(result) callback.onSuccess(result.toInt()) } catch (e: Exception) { println("Fail to calc") callback.onFailure(-1, "Fail") } }
|
为了获取计算的结果,我们传入了callback
参数,用以接收计算结果,或者异常信息。
调用它的代码样例如下,用一个匿名内部对象接收回调:
1 2 3 4 5 6 7 8 9 10 11 12 13
| fun execCalcTaskAsync() { Thread(Runnable { calcSlowly(3, object : CalcTaskCallback<Int> { override fun onSuccess(result: Int) { println("onSuccess, result=$result, in thread: ${Thread.currentThread().name}") }
override fun onFailure(code: Int, msg: String) { println("onFailure, code=$code, msg=$msg, in thread: ${Thread.currentThread().name}") } }) }).start() }
|
这种回调写法在Java中再常见不过了。但是它却有着不小的隐患:
- 嵌套太多,成为“回调地狱”;
- 传入的callback如果是Activity会引起泄露;
- 代码阅读起来不直观。
这几个隐患不详述了,接下来看看在Kotlin中如何将异步回调转换为同步请求。
将异步转化为同步
通过协程的suspendCoroutine
关键字,可以将异步回调转换为同步调用,上例改写方法如下,
1 2 3 4 5 6 7 8 9 10 11 12 13
| suspend fun calcSlowlySync(inp: Int): Int = suspendCoroutine { cont -> calcSlowly(inp, object: CalcTaskCallback<Int> { override fun onSuccess(result: Int) { cont.resume(result) }
override fun onFailure(code: Int, msg: String) { cont.resumeWithException(Exception("code=$code, msg=$msg")) } }) }
|
改写之后,就可以在协程内部愉快地使用这个同步方法进行耗时计算了。
1 2 3 4
| launch(Dispatchers.DEFAULT) { val result = calcSlowlySync(100) println("result=$result") }
|
suspendCoroutine是如何工作的
todo
原始代码MainCoroutine.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| fun main(args: Array<String>) { runBlocking { val job = GlobalScope.launch { val result = calcSlowly(123) println("result = $result") } job.join() } print("FINISH") }
private suspend fun calcSlowly(inp: Int): Int = withContext(Dispatchers.Default) { delay(1000L) inp * 10 }
|
反编译后生成4个文件
- MainCoroutineKt.class,
- MainCoroutineKt$main$1.class
- MainCoroutineKt$main$1$job$1.class
- MainCoroutineKt$calcSlowly$2.class
大纲
混合开发,从把异步请求转换为同步说起
异步接口
转换同步
协程内部如何将异步转换为同步
suspendCoroutine
todo
最后更新时间:
本文系作者原创,如转载请注明出处。欢迎留言讨论,或通过邮件进行沟通~