kotlin中协程相关

协程

  • 用同步的方式写出异步的效果
  • 协程最重要的是通过非阻塞挂起和恢复实现了异步代码的同步编写方式
  • 挂起函数(suspend)不一定就是在子线程中执行的,但是通常在定义挂起函数时都会为它指定其他线程,这样挂起才有意义。挂起函数只能在协程或其他挂起函数中调用。
  • 解决多层嵌套回调

协程不是线程,是基于线程封装的库,可以使用协程库提供的API方便的灵活的指定协程中代码执行的线程、切换线程,但不需要接触线程Thread类。类似于Android的AsyncTask或者RxJava的Schedulers,都解决了异步线程切换的问题,然而协程最重要的是通过非阻塞挂起和恢复实现了异步代码的同步编写方式,把原本运行在不通线程的代码写在一个代码块{}里,开起来就像是同步代码。

launchasync之间的区别

launch是一种适用于“发射并忘记”场景的协程构建器,当你不需要等待协程的结果时,它非常有用。它允许你在后台异步执行任务,而不会阻塞主线程,从而提高应用的响应性。

launch函数返回一个Job对象,我们可以使用这个对象来管理协程的生命周期。

示例:使用launch

fun main() {
    println("Before launch")
    // 启动一个协程
  val job =  GlobalScope.launch {
        delay(1000)
        println("Inside launch")
    }
    println("After launch")
    Thread.sleep(2000)
job.cancel()//取消协程
}

在上面的示例中,我们使用launch关键字在GlobalScope中启动了一个协程。协程通过delay()函数暂停了1000毫秒,然后打印了"Inside launch"。同时,主线程继续执行。输出结果为:Before launch      After launch     Inside launch

async是一种用于异步执行任务并获取其结果的协程构建器。它返回一个Deferred<T>对象,表示将来可用的值,这个对象表示一个可以延期获取结果的异步计算。

示例:使用async并发进行网络请求

suspend fun fetchDataFromAPI(url: String): String {
    // 执行网络请求或其他耗时操作
    return apiService.fetchData(url)
}

suspend fun fetchMultipleData() {
    val deferredData1 = GlobalScope.async(Dispatchers.IO) {
        fetchDataFromAPI("https://api.example.com/data1")
    }
    val deferredData2 = GlobalScope.async(Dispatchers.IO) {
        fetchDataFromAPI("https://api.example.com/data2")
    }
    val data1 = deferredData1.await()
    val data2 = deferredData2.await()
    // 处理获取到的数据
   ```kotlin
    processData(data1, data2)
}

在这个示例中,我们使用async来并发地从多个URL获取数据。每个网络请求被封装在一个单独的async块中,它返回一个Deferred<String>对象。然后我们使用await()来在结果可用时获取结果。获取到数据后,我们可以根据需要进行进一步处理。

总结选择launchasync的关键考虑因素:

  • 当你想要执行一个异步任务而不需要等待结果时,例如进行网络请求或执行后台操作时,使用launch

  • 当你需要并发地执行多个异步任务并获取它们的结果以进行进一步处理时,例如并行网络请求或计算时,使用async

  • 要注意选择合适的上下文来启动协程,以确保正确的线程管理。对于与UI相关的操作,切换到Dispatchers.Main上下文来更新UI。

  • 避免使用长时间运行的操作阻塞主UI线程。使用协程将这些任务转移到后台线程,从而确保响应性用户界面

suspend:withContext(Dispatchers.IO)异步线程

MainScope   需要销毁在onDestroy()方法中:main.cancel()

GlobalScope 全局作用域 默认是异步线程Dispatchers.IO

viewModelScope.launch 默认是主线程Dispatchers.Main

一个页面请求多个接口示例

class SystemViewModel : BaseViewModel() {
    private val remoteRepository: SystemRemoteRepository by lazy {
        SystemRemoteRepository()
    }
    val page = MutableLiveData<Pagination<Article>>()
    fun getArticleList() {
        viewModelScope.launch { //主线程开启一个协程
            // 网络请求:IO线程
            val tree: ApiResult<MutableList<Tree>> =
                RetrofitClient.apiService.getTreeByCoroutines()
            // 主线程
            val cid = tree?.data?.get(0)?.id
            if (cid != null) {
                // 网络请求:IO线程
                val pageResult: ApiResult<Pagination<Article>> =
                    RetrofitClient.apiService.getArticleListByCoroutines(0, cid)
                // 主线程
                page.value = pageResult.data!!
            }
        }
    }
}

/**接口定义,Retrofit从2.6.0版本开始支持协程*/
interface ApiService {
    /*获取文章树结构*/
    @GET("tree/json")
    suspend fun getTreeByCoroutines(): ApiResult<MutableList<Tree>>

    /*根据数结构下某个分支id,获取分支下的文章*/
    @GET("article/list/{page}/json")
    suspend fun getArticleListByCoroutines(
        @Path("page") page: Int,
        @Query("cid") cid: Int
    ): ApiResult<Pagination<Article>>
}
在定义接口时,方法前加了个 suspend 关键字,调用接口的时候用viewModelScope.launch {} 包裹。既然可以运行成功,就说明请求接口并不是在主线程中进行的,然而有的同学不信,他在getArticleList() 方法中的任意位置通过 Thread.currentThread() 打印的结果都Thread[main,5,main],这不就是在主线程中调用的吗?上述协程中的代码是在主线程执行没错,但是接口请求的方法却是在子线程中执行的。
原因就在于我们定义接口的时候使用了suspend 关键字,它的意思是挂起、暂停,函数被加了这个标记就称它为挂起函数,需要注意的是,suspend 关键字并没有其他重要的作用,它仅仅标识某个函数是挂起函数,可以在函数中调用其他挂起函数,但是只能在协程中调用它。所以上面两个接口都被定义为挂起函数了,挂起函数只能在协程中调用,那谁是协程?
其实在 kotlin 协程库中是有一个类 AbstractCoroutine 来表示协程的,这个抽象类有很多子类代表不同的协程,但是这些子类都是private 的,并没有暴露给我们,所以你在其他文章中看到别人说
viewModelScope.launch{} 包裹起来的闭包 ( 代码块 ) 就是协程也没问题,但这个代码块的真正意义是协程需要执行的代码。当在协程中调用到挂起函数时,协程就会在当前线程(主线程)中被挂起,这就是协程中著名的 非阻塞式挂起,主线程暂时停止执行这个协程中剩余的代码,注意:暂停并不是阻塞等待(否则会ANR ),而是主线程暂时从这个协程中被释放出来去处理其他 Handler 消息,比如响应用户操作、绘制View 等等。
那挂起函数谁执行?这得看挂起函数内部是否有切换线程,如果没有切换线程当然就是主线程执行了,所以挂起函数不一定就是在子线程中执行的,但是通常在定义挂起函数时都会为它指定其他线程,这样挂起才有意义。比如上面定义的suspend 的请求接口, Retorift 在执行请求的时候就切换到了子线程并挂起主线程,当请求完成(挂起函数执行完毕)返回结果时,会通知主线程:我该干的都干完了,下面的事你接着干吧,主线程接到通知后就会拿到挂起函数返回的结果继续执行协程里面剩余的代码,这叫做协程恢复(resume) 。如果又遇到挂起函数就会重复这个过程,直到协程中的代码被执行完。
通过协程可以彻底去除回调,使用同步的方式编写异步代码。什么是同步调用?调用一个方法能直接拿到方法的返回值,尽管这个方法是耗时的、在其他线程执行的,也能直接得到它的返回值,然后再执行下面的代码,协程不是通过等待的方式实现同步,而是通过非阻塞挂起实现看起来同步的效果。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值