Skip to content

Kotlin协程的官方库

更新: 12/9/2025 字数: 0 字 时长: 0 分钟

基本使用

Kotlin官方为我们提供的协程启动方法是launch方法,所以我们可以先试一下

kotlin
launch{

}

我们会发现存在一个警告,这是因为Kotlin希望我们的launch方法全部基于一个Scope,我们先来试一下GlobalScope

kotlin
GlobalScope.lauch{

}

现在我们会发现lauch没问题了,但是GlobalScope却出现了问题就没问题了,这是因为GlobalScope不够安全,没有结构化并发的功能

当前Kotlin推荐的协程书写方法是基于CoroutineScope来实现,CoroutineScop为我们提供了一种结构化并发的协程模型,即如果父协程失败或被取消,子协程也会被递归的取消,这种方式更加的安全

如果想要创建结构型,则需要使用coroutineScope方法

kotlin
fun main():Unit = runBlocking { //这里使用runBlocking是因为coroutioneScope本身是一个挂起方法
    coroutineScope {  
        this.launch {  
            this.launch {  
                delay(2.seconds)  
                println("Child of the enclosing coroutine completed")  
            }  
            println("Child coroutine 1 completed")  
        }  
        this.launch {  
            delay(1.seconds)  
            println("Child coroutine 2 completed")  
        }  
    }  
}

我们再来看一下lauch方法

kotlin
public fun CoroutineScope.launch(  
    context: CoroutineContext = EmptyCoroutineContext,  
    start: CoroutineStart = CoroutineStart.DEFAULT,  
    block: suspend CoroutineScope.() -> Unit  
): Job {
	//。。。
}

context我们并不陌生,就是我们之前提到的调度器,而start是指协程的启动模式,协程启动模式分为四种

  • DEFAULT:协程创建后,立即开始调度,在调度前如果协程被取消,其将直接进入取消响应的状态。
  • ATOMIC:协程创建后,立即开始调度,协程执行到第一个挂起点之前不响应取消。
  • LAZY:只有协程被需要时,包括主动调用协程的start、join或者await等函数时才会开始调度,如果调度前就被取消,那么该协程将直接进入异常结束状态。
  • UNDISPATCHED:协程创建后立即在当前函数调用栈中执行,直到遇到第一个真正挂起的点。

如果想要搞清楚这几个模式的效果,我们就要先弄清楚立即调用和立即执行有什么区别,所谓立即调度,就是指协程会立即接受调度指令,但协程具体什么时候执行以及在哪个线程上执行则需要调度器自己安排,换言之,就是立即调度距离立即执行还有一段时间,由此我们可以得出:

  • DEFAULT是立即调度,但可能在执行前被取消
  • UNDISPATCHED是理解执行,因此不论执行多少,协程都会被执行
  • ATMOIC是立即调度,但由于到第一个挂起点前不响应取消,因此一定会执行一段时间,就像他的名字一样,是一个原子的操作
  • UNDISPATCHED和ATOMIC虽然都会保证协程一定执行,但在第一个挂起点之前,前者运行在协程创建时所在的线程,后者则会调度到指定的调度器所在的线程上执行。

在业务开发中,我们用的最多的实际上是DEFAULT和LAZY两种模式

接着我们再来看调度器,我们已经知道调度器再协程中的作用,我们现在来看一下Kotlin官方为我们准备了哪些调度器

  • Default:默认调度器,适合后台计算操作,用于CPU密集型操作
  • IO:IO调度器,适合IO相关操作,适合IO密集型操作
  • Main:UI调度器,根据不同的平台会初始化对应的UI线程的调度器,通常在主线程上执行,主要用于客户端开发
  • Unconfined:“无所谓”调度器,不要求协程执行在特定线程上。协程的调度器如果是Unconfined,那么它在挂起点恢复执行时会在恢复所在的线程上直接执行,当然,如果嵌套创建以它为调度器的协程,那么这些协程会在启动时被调度到协程框架内部的事件循环上,以避免出现StackOverflow。

我们尝试使用一下Main调度器

kotlin
fun main():Unit = runBlocking {  
    coroutineScope {  
        this.launch(Dispatchers.Main) {  
  
        }
    }  
}

运行代码后会发现直接报错,这是因为Dispatcher.Main仅限于在特定平台(Android,Swing,Fx等)使用,而我们启动的普通的服务端应用是不符合他的要求的

然后再看Default和IO这两个调度器的实现,我们会发现二者使用了同一个线程池,这是因为IO密集型操作大多时间都是在阻塞等待IO操作的完成,阻塞期间再占用线程是一个十分不合理的操作,因此IO调度器对于IO任务做了并发量的限制,避免过多的IO任务并发占用太多的系统资源,同时在调度时为任务打上PROBABLY_BLOCKING的标签,以方便线程池在执行任务调度时对阻塞任务和非阻塞任务区别对待。

当然,我们也可以自定义一个调度器出来,只需要继承Kotlin准备的CoroutineDispatcher抽象类,亦或是通过asCoroutineDispatcher来将一个线程池转换为调度器

kotlin
private object VirtualThreadDispatcherHolder {  
    val instance: CoroutineDispatcher by lazy {  
        Executors.newVirtualThreadPerTaskExecutor()  
            .asCoroutineDispatcher()  
    }  
}  
val Dispatchers.VIRTUAL: CoroutineDispatcher  
    get() = VirtualThreadDispatcherHolder.instance

我们自己写的调度器没有一个很好的生命周期,所以需要我们手动将其关闭

kotlin
Executors.newVirtualThreadPerTaskExecutor()  
    .asCoroutineDispatcher()  
    .use{  
        val result = withContext(it) {  
            delay(100)  
            "Hello World"  
        }  
    }

像这样,我们就定义出了一个基于Java23的VituralThread的调度器,不过更好的适配还是等待更新的Kotlin协程库官方推出吧

我们也可以使用withContext实现创建协程

kotlin
withContext(Dispatchers.IO){  
  
}

withContext只要求传入调度器即可使用,其最大的意义在于再客户端开发(如安卓)中将代码切换到非主线程/UI线程中进行执行

常用的创建协程的函数

我们再来列举一下Kotlin中的创建协程的各种方法

kotlin
coroutineScope {   
	val job=launch {   
          
    }  
}
  • CoroutineScope.async():启动协程,且有返回值,返回值为Deferred,我们可以通过await方法来获取协程最后的返回值
kotlin
coroutineScope {   
	val job=async {   
          1
    }  
	job.await() //1
}
  • runBlocking():启动一个协程,协程自己会导致运行他的线程阻塞,本身返回协程代码块的返回值
kotlin
fun main(){
    println(measureTime {
        runBlocking {
            launch {
                delay(2000)
            }
        }
    })//2s
}
  • withContext():让协程在指定的调度器中运行,用于切换上下文,该方法会直接返回协程代码块的返回值
  • coroutineScope():创建结构性的协程,本身返回协程代码块的返回值

这些方法都可以创建出协程定义域,值得注意的是,withContextcoroutineScope这两个方法都可以直接获取其协程代码块中的返回值,但这两个函数并不会阻塞运行其自身的协程/线程,这是Kotlin在后台做出的实现,当你使用其时,会挂起执行他的线程,等待其内部内容执行完毕才会继续运行后面的内容,因此withContext和coroutineScope都会导致其代码停在调用它的地方,进而使用异步的方法写出同步的代码

取消检查

Kotlin协程中存在yield方法,其可以检查协程是否被取消,这里就要引入一个新的知识点,协程的取消机制,Kotlin中的协程是协作式取消,也就是说,Kotlin的协程是主动运行到一个可取消点后才会取消,Kotlin协程的可取消点有:

  • 几乎所有的挂起函数
  • yield() / ensureActive()(显式取消点)
  • withContext(...)/coroutineScope {} 结构化并发边界(隐式可取消点)

只有在这些时候协程才会执行用户在外部的取消操作

而yield一旦发现协程在运行到当前位置被取消了就会立刻报错

另一方面,yield还有强制协程让出当前线程的功能,就是协程主动的将自己挂起,这里要注意的一点是,yield只会让协程在当前时刻将自己挂起,但不排除调度器立刻将协程切换回来的情况,一个经典的例子就是,当只有一个协程被启动时,如果自身执行yield函数会立刻挂起自己,然后调度器寻找其他等待空余线程的协程,发现不存在其他等待协程,就会再立刻切回到执行yield的协程,进一步的讲,yield只负责当前时刻讲协程占用的线程让出,下一时刻的事yield就不管了

禁止取消

Kotlin中有一个神奇的上下文,Noncallable,该上下文的最大意义在于可以禁止外界对协程的取消

kotlin
withContext(NonCancellable) {  
    printSomeThingBlock("hello")  
}

需要注意的是,该上下文只能与withContext函数结合使用

本站访客数 人次      本站总访问量