Kotlin 协程的使用

本文详细介绍了Kotlin协程的使用,包括协程与线程的区别、使用场景,以及在Kotlin 1.1中如何创建和控制协程行为,通过实例展示了协程的延迟生成器和与线程的对比。

Kotlin 协程


协程(Coroutine)与线程(Thread)

协程和线程的区别

协程和线程的共同目的之一是实现系统资源的上下文调用,不过它们的实现层级不同;

线程(Thraed)是比进程小一级的的运行单位,多线程实现系统资源上下文调用,是编程语言交付系统内核来进行的(可能是并发,也可能是伪并发),大部分的编程语言的多线程实现都是抢占式的,而对于这些线程的控制,编程语言无法直接控制,需要通过系统内核来进行,由系统内核决定最终的行为;

协程(Coroutine)是在语言层面实现“多线程”这个过程,一般在代码中以串行的方式表达并发逻辑,由于是在编程语言层面模拟这一过程,而非涉及到硬件层面,在编程语言层面可以完全控制这一过程,可以这么说,协程是软件层面模拟硬件层面的多线程;但不是说协程就一定是单线程,具体的实现要看具体编程语言的实现,kotlin的协程实现可能是单线程,也可能是多线程;

协程的使用场景

协程可以用于解决高负荷网络 IO、文件 IO、CPU/GPU 密集型任务等;

比如在IO线程高负载的场景下,CPU资源会被大量线程占用,这会极大浪费CPU资源,同时可能导致一些重要的线程被阻塞,代价是十分昂贵的,此时如果使用IO协程代替IO线程,可以大大减少线程数量,节省CPU资源,同时在协程挂起是几乎无代价的(不需要上下文切换或OS干预),同时编程语言对协程又有极大控制性;


Kotlin 1.1 中使用协程

Kotlin 1.1 开始提供了对于协程的支持,不过目前 kotlin 1.1 版本的协程处于实验性阶段,相关的包不包含在标准库中,需要手动导入"kotlinx-coroutines-core"包到项目依赖,可以直接 maven 中央仓库找到最新的 jar 包;
如果是使用 gradle 构建项目,在 build.gradle 脚本中需要添加类似以下:
apply plugin: 'java'
apply plugin: 'kotlin'
......
dependencies {
    compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.19.1'    //添加相关包依赖
    ......
}
kotlin{             //开始协程实验性功能
    experimental{
        coroutines 'enable'  
    }
}


kotlin 对于协程提供了底层的API,同时提供了高级API方便使用,这里只介绍部分高级API的基本使用;

一个基本协程例子

import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch
​
fun main(args: Array<String>) {
    async(CommonPool) {    //创建一个协程
        delay(1000L)       //协程挂起
        log("World")
    }
    log("Hello,")
    Thread.sleep(2000L)      //主线程等待
}
​
//输出简要日志,以下例子使用了这个函数作简要日志输出,不再重复解释
fun log(message:String){ println( "[${Thread.currentThread().name}] $message") }


输出:
[main] Hello,
[ForkJoinPool.commonPool-worker-1] World



协程的创建

kotlin 中可以通过 async() 函数创建一个协程,该协程创建后立即运行,一个 async 中可以包含一个 使用"suspend" 关键字声明的挂起函数,或使用一个 lambda 表达式表示的匿名方法,如下:
import kotlinx.coroutines.experimental.*;
​
//async 使用suspend函数创建协程
fun main(args: Array<String>) {
    async(CommonPool) {  //协程创建
        foo()
    }
    log("Hello,")
    Thread.sleep(2000L)
}
suspend fun foo(){   //挂起函数
    delay(1000L)
    log("World")
}
​
//async 使用匿名函数创建协程
fun main(args: Array<String>) {
    async(CommonPool) {   //协程创建
        delay(1000L)
        log("World")
    }
    log("Hello,")
    Thread.sleep(2000L)
}


在主线程上创建协程

以上的示例都是在 CommonPool 公共线程池上创建协程的,在main主线程中创建协程,可以如下标记 main:
fun main(args: Array<String>) = runBlocking<Unit>{
    async(CommonPool)  {
        delay(1000L)
        log("World")
    }
    log("Hello,")
    delay(2000L) //此时main主线程也为协程,可以使用其他协程行为函数
}


 

协程的行为控制

kotlin 1.1 对于协程提供了以下常用的控制方法:
dely(millis:Long):协程挂起
Deffered.join():类似Thread.join
Deffered.cancel():协程取消,可携带一个 Exception 实例作为参数,以在协程取消时抛出该异常,可以通过返回值判断协程是否取消成功
Deffered.await():等待直接获取协程返回值
/*演示join()
 在调用job.join时,main协程会挂起,直到job执行结束*/
fun main(args: Array<String>) = runBlocking<Unit> {
    val job = async(CommonPool) {
        delay(1000L)
        log("World!")
    }
    log("Hello,")
    job.join()
}
/*output:
[main] Hello,
[ForkJoinPool.commonPool-worker-1] World! */
​
​
/*演示cancel() */
fun main(args: Array<String>) = runBlocking<Unit> {
    val job = async(CommonPool) {
        delay(1000L)
        log("World!")
    }
    log("Hello,")
    job.cancel()
}
/*output:
[main] Hello, */
​
​
/*演示await()
 以下示例演示了一个典型的异步过程,在执行work()之前,异步执行前置任务preWork1(),preWork2(),等待这两个
 任务完全执行结束后,再执行word();
 如果使用多线程来编写,需要些大量的回调函数,协程提供了一种简洁的编写方式*/
fun main(args: Array<String>) = runBlocking {
    val job1 = async(CommonPool) { preWork1() }
    val job2 = async(CommonPool) { preWork2() }
    work(job1.await(),job2.await())
}
suspend fun preWork1():String{
    delay(3000L)   //模拟一个耗时任务
    return "job1:van ♂ yang"
}
suspend  fun preWork2():String{
    delay(1000L)   //模拟一个耗时任务
    return "job2:dark fantastic!"
}
fun work(str1:String,str2:String){
    println("$str1\n$str2")
}
/*output:
job1:van ♂ yang
job2:dark fantastic! */



延迟生成器

kotlin 协程还提供了类似 Python 的生成器协程函数 buildSequence,用于执行一个延迟计算队列;
一个典型的应用就是生成斐波那契数列,一般编写会用递归的方式来编写,但是假如递归层级过大,很容易造成栈溢出,同时假如需要多次不等值地调用,会造成同一个值被多次计算,比较浪费资源,buildSequence 采用协程的方式,在每次调用时进行增量计算,避免同一值的多次调用,示例如下:
fun main(args: Array<String>) {
​
    //斐波那契数列
    val fibonacci = buildSequence {
        yield(1) //返回第一个斐波那契数
        var cur = 1
        var next = 1
        while (true) {
            yield(next) // 返回下一个斐波那契数
            val tmp = cur + next
            cur = next
            next = tmp
        }
    }
    //取出>100的斐波那契数
    for (i in fibonacci){
        println(i)
        if(i > 100) break
    }
  
    //取出>500的斐波那契数,此时调用时 1-100 的斐波那契数是已经被延迟队列计算好的了
    for (i in fibonacci){
        println(i)
        if(i > 500) break
    }
}



协程和线程的对比示例

上面讲过协程很适合使用在高负荷IO、CPU密集型的场景,通过协程模拟并发的方式减少实际的线程数,已达到节省CPU资源的目的,以下2个示例分别创建 100,000 个线程和 100,000个协程模拟一个高负荷IO的场景:

创建 100,000 个线程
fun main(args: Array<String>) = runBlocking<Unit> {
    val jobs = List(100_000) {
        thread {
            Thread.sleep(2000L)   //模拟IO阻塞
            log("hello")
        }
    }
    jobs.forEach(Thread::join)
}


此时我的机器上创建了几千个线程后抛出栈溢出异常(-Xmx1024),同时CPU占满(i7-6700HQ)

创建 100,000 个协程
fun main(args: Array<String>) = runBlocking<Unit> {
    val jobs = List(100_000) {  
        launch(CommonPool) {
            delay(2000L)   //模拟IO阻塞
            print("hello")
        }
    }
    jobs.forEach { it.join() } //这里不能用 jobs.forEach(Job::join),因为 Job.join 是 suspend 方法
}


程序运行时只创建了不到10个线程,并没有栈溢出,相比之下,在这样的场景里更适合使用协程;













Kotlin 协程Kotlin 提供的一套简化异步编程的轻量级线程操作框架,适合 Android 开发中的异步任务处理,以下是关于它的使用指南: ### 核心组件使用 - **CoroutineScope**:这是一个与生命周期绑定的作用域,比如 `viewModelScope`。它可以确保协程在特定的生命周期内执行,避免内存泄漏。示例代码如下: ```kotlin import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch class MyViewModel : ViewModel() { fun performTask() { viewModelScope.launch { // 协程内执行的任务 } } } ``` - **Dispatcher**:调度器,可指定协程在哪个线程或线程池执行,有 `IO`、`Default`、`Main`、`Unconfined` 等类型。例如,进行网络请求时可使用 `IO` 调度器: ```kotlin import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking fun main() = runBlocking { launch(Dispatchers.IO) { // 执行 IO 密集型任务,如网络请求 } } ``` - **Job**:用于控制协程任务的生命周期。可以启动、取消协程等操作。示例如下: ```kotlin import kotlinx.coroutines.* fun main() = runBlocking { val job = launch { // 协程任务 } // 取消协程 job.cancel() } ``` - **挂起函数**:用 `suspend` 标记的可暂停函数。它只能在协程或其他挂起函数中调用。例如: ```kotlin import kotlinx.coroutines.* suspend fun doSuspendWork() { delay(1000) // 模拟耗时操作 println("Suspend work done") } fun main() = runBlocking { launch { doSuspendWork() } } ``` ### 基础使用场景 - **网络请求封装**:使用协程可以方便地进行网络请求,并且可以在协程中处理请求结果。示例代码如下: ```kotlin import kotlinx.coroutines.* import retrofit2.Retrofit import retrofit2.http.GET // 定义 API 接口 interface ApiService { @GET("data") suspend fun getData(): String } fun main() = runBlocking { val retrofit = Retrofit.Builder() .baseUrl("https://example.com/") .build() val api = retrofit.create(ApiService::class.java) launch { try { val result = api.getData() println("Received data: $result") } catch (e: Exception) { println("Error: ${e.message}") } } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值