我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Kotlin协程与挂起函数及suspend关键字深入理解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Kotlin协程与挂起函数及suspend关键字深入理解

1.挂起函数

挂起函数在Kotlin协程中是一个比较重要的知识点,协程的非阻塞式、Channel、Flow等API都对它有充分的理解才能在学习时事半功倍。

已知的是Kotlin协程的特点是轻量和非阻塞, 单靠这两个点就能说明Kotlin协程就的优势吗,不一定。这里先提出一个结论:挂起函数是Kotlin协程的最大优势。 下面对于挂起函数的讲解就围绕这句话展开。

以获取省市区Code为例,获取区域Code的前提是要有城市Code,城市Code的前提是要有省份Code,请求结果通过CallBack返回。

public class RequestCode {
    public static void main(String[] args) {
        getProvincesCode(provincesCode -> {
            getCityCode(provincesCode, cityCode -> {
                getAreaCode(cityCode, areaCode -> {
                });
            });
        });
    }
    
    private static void getProvincesCode(CallBack callBack) {
        callBack.onSuccess("100000");
    }
    
    private static void getCityCode(String provincesCode, CallBack callBack) {
        callBack.onSuccess("100010");
    }
    
    private static void getAreaCode(String cityCode, CallBack callBack) {
        callBack.onSuccess("100011");
    }
}

上面的代码在开发中都遇到过,代码可以优化的更简洁更易读,这里主要是证明挂起函数的优势,就不做优化了。

上面的代码可以看到三层嵌套是比较复杂的,如果再加上获取国家、区域街道的话嵌套层级会更深,这样就会对可读性、可扩展性、可维护性都有影响。那么用Kotlin这个代码要怎么写呢?

现在我用delay(1000L)替代CallBack模拟网络请求

fun main() = runBlocking {
    val provincesCode = getProvincesCode()
    val cityCode = getCityCode(provincesCode)
    val areaCode = getAreaCode(cityCode)
}

private suspend fun getProvincesCode(): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "100000"
}

private suspend fun getCityCode(provincesCode: String): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "100010"
}

private suspend fun getAreaCode(cityCode: String): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "100011"
}

Kotlin实现了同步方式完成异步请求,这种方式的实现要归功于具体的函数的实现,在这三个函数中可以发现他们的定义与普通函数的区别是多了一个suspend关键字,它的意思就是挂起。

再回头看main函数,前面加上了runBlocking也就是说建立了一个协程的作用域,这是因为suspend的出现,因为suspend的作用就是挂起和恢复,而挂起和恢复是需要上下文的,因此就需要定义一个作用域,这里选择了runBlocking

在函数中还有一个地方withContext(Dispatchers.IO)这主要是执行线程的切换,除了IO线程以外还有Maindefault等线程。

在Kotlin中挂起和恢复是成对出现的,因为既然会被挂起那肯定也会被恢复,而协程的非阻塞也是因为挂起和恢复能力,那么挂起和恢复又是怎么样的一个含义呢?

首先执行getProvincesCode()这是一个挂起函数,因为用了Dispatchers.IO切换到了IO线程因此这个等待时间就在IO线程执行,当等待时间结束后(CallBack回调结果)provincesCode收到返回的结果,这个函数中suspend挂起,main函数中收到结果就是恢复能力,

我们看一下debug日志:

  • 这是getProvincesCode的协程日志,可以获取以下几个信息:
  • 当前协程的名字是:coroutine#1
  • 协程运行在主线程:main
  • 任务被挂起

withContext(Dispatchers.IO)切换线程

任务切换到DefaultDispatcher-worker-1线程继续执行

  • return "省:100000"回到主线程执行

我们总结一下上面的日志:

  • mainDefaultDispatcher-worker-1的过程是挂起;
  • DefaultDispatcher-worker-1main的过程是恢复;

那么挂起和恢复的含义也有非常明确了:

  • 挂起: 只是将程序执行流程转移到了其他线程,主线程并不会阻塞。我们知道Android中如果阻塞超过一定时间就会出现无响应,上面的代码在运行过程中并不会导致无响应的发生,在代码执行的过程中我们还可以做其他事情的,因为任务进入到子线程后主线程的状态是空闲的。
  • 恢复: 当子线程的任务执行完毕后再回到主线程的过程就叫做恢复。

挂起和恢复的能力是挂起函数特有的能力,普通函数时不具备的,如果在一个普通函数中仅仅加上suspend关键字会发现其实并没有什么用,编译器会告知这种定义是多余的。

上面还提出了一个结论——挂起函数是Kotlin协程的最大优势, 首先因为函数的执行过程中可以切换线程,其次函数的运行不会影响主线程导致主线程被阻塞。

2.深入理解suspend

上面挂起函数,挂起函数主要就是主线程切换到子线程,这个过程又是如何实现的?

已知挂起函数是依靠关键字suspend,我们将带有这个关键子的函数转换成Java代码进行分析:

private static final Object getProvincesCode(Continuation var0) {
    ...
    CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO();
    ...
    return "省:100000";
}

代码比较长,只保留了需要的地方。

  • getProvincesCode里面多了一个参数Continuation,意思是延续,而suspend没有了,那么Continuation又是什么
public interface Continuation<in T> {
    
    public val context: CoroutineContext
    
    public fun resumeWith(result: Result<T>)
}

Continuation的源码发现一个问题,它跟我们开头的CallBack是类似的,其中resumeWithonSucess的功能也是一样的,区别在于Continuation是有一个泛型的参数

public interface CallBack {
    public void onSuccess(String response);
}
public interface Continuation<in T> {
    public fun resumeWith(result: Result<T>)
}

由此可以得出结论:Continuation本质上就是CallBack只是多了一个带有泛型的参数。

通过上面的分析可以得出结论 :挂起函数的本质就是Callback

我们已知Continuation的本质是CallBackContinuation是延续,就是接下来要做的事情,那么在省市区获取的代码其实就是这样一个流程:

getProvincesCode(object : Continuation<String> {
    override fun resumeWith(result: Result<String>) {
        val provinceCode = result.getOrNull()
        println("provincesCode:$provinceCode")
        getCityCode(provinceCode, object : Continuation<String> {
            override fun resumeWith(result: Result<String>) {
                val cityCode = result.getOrNull()
                println("cityCode:$cityCode")
                getAreaCode(cityCode, object : Continuation<String> {
                    override fun resumeWith(result: Result<String>) {
                        val areaCode = result.getOrNull()
                        println("areaCode:$areaCode")
                    }
                })
            }
        })
    }
})

这个过程是编译器在后面帮我们做的,实际编码中这种编码方式并不会出现,毕竟挂起函数解决的就是这个问题。

3.协程与挂起函数

需要说明的是协程与挂起函数并不是同一个东西,我们再最开始使用suspend实现省市区三级联动的时候用到了runBlocking,挂起函数的调用是需要一个协程作用域的,除了这点之外,在runBlocking的源码中还有一点要注意:

public actual fun <T> runBlocking(
    context: CoroutineContext, 
    block: suspend CoroutineScope.() -> T): T {
    ...
}

第二个参数中有一个suspend,所以可以理解CoroutineScope.() -> T也是一个挂起函数那么被suspend关键字修饰的挂起函数可以运行在runBlocking中也就不难理解了。

那么我们就可以得到一个结论:挂起和恢复是协程的底层能力,而挂起函数是一种表现形式,通过suspend关键字修饰的函数可以让我们在上层很方便的实现这种底层能力。

4.挂起函数是Kotlin协程的最大优势

开篇就提出了这个结论,最后再来总结一下这个能力:

  • 挂起函数在执行中可以切换线程且不会对主线程造成阻塞;
  • 挂起函数有挂起和恢复的能力,可以极大地简化异步编程,实现同步执行异步任务;
  • 挂起函数是Kotlin中特有的能力;

5.总结

  • 定义一个挂起函数只需要在普通函数上加上suspend关键字,而添加这个关键字之后函数类型就会被改变,如suspend (Int) -&gt; Double”与“(Int) -> Double并不是同一个类型;
  • 挂起函数具有挂起和恢复的能力,那么就会出现同一行代码会在两个线程中执行,Kotlin编译器会在后台进行编译;
  • 挂起函数的本质就是CallBack。只是说,Kotlin 底层用了一个更加高大上的名字,叫 Continuation;
  • 挂起和恢复是一种底层能力,而上层的表现形式是挂起函数;
  • 挂起函数只能在协程中被调用或者在挂起函数中调用,而协程中的block也是一个挂起函数。

以上就是Kotlin 协程与挂起函数及suspend关键字深入理解的详细内容,更多关于Kotlin 协程挂起函数suspend的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Kotlin协程与挂起函数及suspend关键字深入理解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Kotlin协程与挂起函数及suspend关键字深入理解

这篇文章主要为大家介绍了Kotlin协程与挂起函数及suspend关键字深入理解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-08

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录