Kotlin协程学习

此文章仅仅记录用法学习,不从源码与线程概念分析

一、协程开启方式

1.使用runBlocking代码块

runBlocking {
       //耗时操作
}

注意:不建议使用,此方法开启的协程仍然是主线程,如果有在此做耗时操作,会阻塞主线程
使用场景:测试用

2.使用CoroutineScope(Dispatchers.IO),参数可以选择在io线程还是main线程来,根据启动方式可以返回job或者Def类型,job中有相关api比如cancel、join等等,def返回的是有值函数,和java中的future类似

//方式1
val job = CoroutineScope(Dispatchers.IO).launch {
       //耗时操作     
}

//方式2
val def = CoroutineScope(Dispatchers.IO).async {
      //耗时操作    
}

注意:协程需要在activity销毁的时候取消,避免内存泄漏,如下

job.cancel()
def.cancel()

使用场景:io数据库操作、网络请求等

3.全局协程,单例,同样有launch和asyn方式

val job = GlobalScope.launch(Dispatchers.IO){
      //耗时操作   
}

注意:此协程是单例且全局性的,尽量少用,容易造成内存泄漏。
使用场景:应用不会频繁调用的耗时操作,且不太用关注结果的,比如比较耗时的初始化操作

4.lifecycleScope.launch(Dispatchers.IO),默认绑定生命周期的协程,同样有有launch和asyn方式

lifecycleScope.launch(Dispatchers.IO){
      //耗时操作   
}

使用场景:和activity强绑定的耗时操作,比如页面的网络加载、数据库加载等等

5.使用MainScope().launch(Dispatchers.IO),同样有launch和asyn方式

MainScope().launch(Dispatchers.IO){
      //耗时操作   
}

注意:协程需要在activity销毁的时候取消,避免内存泄漏

6.viewModel绑定的协程,一般用于MVVM结构,跟随viewmodel生命周期绑定,同样有launch和asyn方式

viewModelScope.launch(Dispatchers.IO) {
      //耗时操作   
}

二、用法场景

此节主要讲一下,协程中常用的withContext、delay、suspend、Deferred到底是什么意思,用法是什么,想必这也是困扰大家很多的地方
1.withContext()
协程切换,与上文的创建协程不同,withContext仅仅做协程的切换。我们举个场景,我们平时线程中做数据库访问或者网络访问后,得到了数据要进行UI修改是怎么做的?是不是比较麻烦,需要封装消息然后发送handler?

class UpdateUIHandler():Handler(Looper.getMainLooper()) {
    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        when (msg.what) {
            1 ->{
                //update ui
            }
        }
    }
}
ThreadPoolManager.getInstance().submit(Runnable { 
        //耗时操作
        //....
        val message = Message();
        message.what = 1
        handler.sendMessage(message)
})

网上看代码是不是很复杂,那么用协程就比较简单了,我们开启一个io协程,等到结果返回后直接withContext(Dispatcher.Main)就能切换到主线程,优化代码如下:

CoroutineScope(Dispatchers.IO).launch {
    //做耗时操作
    //...
    withContext(Dispatchers.Main) {  //切换到主线程
        //update UI
    }
}

是不是很简单呢?当然你如果在主线程想要切换带io线程,那么也可以withContext(Dispatcher.IO)这么来
withContext:不启动新协程,仅切换上下文,协程挂起直至块完成。

2.delay
delay这个方法其实和Thread.sleep很相似,但是区别在于delay是协程的一个特性,它是可以挂起的,且不会阻塞主线程。比如

CoroutineScope(Dispatchers.Main).launch {
    delay(1000)   //虽然在主线程,但是不会阻塞主线程
}

CoroutineScope(Dispatchers.Main).launch {
    Thread.sleep(1000)  //会阻塞主线程
}

3.suspend挂起函数
这个也是困扰了我很久。挂起函数(Suspending Function),从字面上理解,就是可以被挂起的函数。suspend 有:挂起,暂停的意思。在这个语境下,也有点暂停的意思。暂停更容易被理解,但挂起更准确。
我们举个场景:我们要获取学生的数学课程的成绩,首先根据学号获取到学生的成绩单子,再根据成绩单号查询对应的课程号,最后根据课程号查询到成绩,伪代码如下

getStudentNo(object: CallBack(){   //获取学生学号
   override fun success(no: Int) {
       getScoreList(object: CallBack(){   //根据学生学号获取成绩单号
           override fun success(listNo: Int){
               getMathScore(object:Callback() {
                   override fun success(MathNo: Int){  //根据成绩单号,查询数学成绩

                   }
                   override fun fail() {
                   }
               },listNo)
           }
           override fun fail() {
           }
       },no)
   }
   override fun fail() {
   }
})

可以看到这种情况下回调非常多,因为首先要网络请求等到学号数据返回,然后根据学号再查成绩单号,查到成绩单号后再根据这个查询数学成绩,整个过程都是网络耗时操作,且代码可读性可维护性比较差,这就是所谓的回调地狱(callback hell)
那么协程中的挂起就能很好的解决这个情况了,我们可以把代码写的很优雅,如下:

CoroutineScope(Dispatchers.Main).launch {
	val studentNo = getStudentNo()
	val scoreNo = getScoreList(studentNo)
	val mathScore = getMathScore(scoreNo)
}

上述三个函数都用suspend修饰,拿一个举例,其中withContext会挂起,直到运行完毕才会return给主线程

suspend fun getStudentNo(): Int{
    withContext(Dispatchers.IO) {
        Thread.sleep(1000)  //模拟耗时
    }
    return 114514
}

注意:
(a)suspend只有在函数内部有delay、withContext、await时才会生效,且能够真正的暂停(挂起)函数

(b)
下文中代码片的情况,无论do other是什么,1和2逻辑可以看作会同时走,哪怕包围此逻辑的代码中有suspend关键词

CoroutineScope(Dispatchers.IO).launch{
 //do other1
}
//do other2

4.Deferred
是async开启协程返回的一个对象,此对象可以通过.await等待结果

val def = CoroutineScope(Dispatchers.IO).async {
    Thread.sleep(4000)   //模拟耗时操作
    val i = 1999
    i
}
val result = def.await()

工作案例需求

1.假设getScore()方法是一个访问数据库耗时操作,主线程怎么通过协程的方式来等待这个线程访问结果,且不阻塞主线程呢?
方式1:getScore()设置成suspend挂起,并使用Deferred.await

fun main() {
    CoroutineScope(Dispatchers.Main).launch {
        updateUI(getScore())
    }
}

suspend fun getScore():Int {
    val def = CoroutineScope(Dispatchers.IO).async {
        Thread.sleep(3000)   //模拟网络操作
        100
    }
    return def.await()
}

方式2:访问数据库结果后通过withContext()切到主线程操作

fun getScore() {
    CoroutineScope(Dispatchers.IO).launch {
        Thread.sleep(3000)   //模拟网络操作
        val score = 100
        withContext(Dispatchers.Main) {  //切到主线程
            updateUI(score)
        }
    }
}

2.funA()是一个耗时任务,执行完毕后funB()才能执行,且不能主线程阻塞
方式1:funA和funB设置挂起函数,使用withContext

suspend fun funA() {
    withContext(Dispatchers.IO){
        Thread.sleep(2000)  //模拟耗时
    }
}

suspend fun funB() {
    withContext(Dispatchers.IO){
        Thread.sleep(2000)
    }
}

fun main(){
    CoroutineScope(Dispatchers.Main).launch {
	    funA()
	    funB()
    }
}

方式2:funA和funB使用deferred.await

fun funA() {
    return CoroutineScope(Dispatchers.IO).async{
        Thread.sleep(2000)
    }
}

fun funB() {
    return CoroutineScope(Dispatchers.IO).async{
        Thread.sleep(2000)
    }
}

fun main(){
    CoroutineScope(Dispatchers.Main).launch {
	    funA().await()   //如果不使用await(),会导致funA和funB同时执行
	    funB().await()
    }
}

方式3:使用job的join,等待执行完毕

fun funA() {
    return CoroutineScope(Dispatchers.IO).launch{
        Thread.sleep(2000)
    }
}

fun funB() {
    return CoroutineScope(Dispatchers.IO).launch{
        Thread.sleep(2000)
    }
}

fun main(){
    CoroutineScope(Dispatchers.Main).launch {
        funA().join() //等待a执行完毕
        funB()
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C_lea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值