有关kotlin中的runBlocking和coroutineScope

作者水平有限,大多数表达为当前作者对kotlin的理解

本文主要用途为学习记录,内附大量可能不正确的个人推测,欢迎大佬们进行指正!

 

对这两个东西产生这个疑问的基本都是看了官方教程吧?教程中有这么一句话

runBlocking与coroutineScope可能看起来很类似,因为它们都会等待其协程体以及所有子协程结束。这两者的主要区别在于,runBlocking方法会阻塞当前线程来等待,而 coroutineScope只是挂起,会释放底层线程用于其他用途。由于存在这点差异,runBlocking 是常规函数,而coroutineScope是挂起函数。

fun fun0() = runBlocking { // this: CoroutineScope
    launch {
        delay(200L)
        println("Task from runBlocking")
    }

    coroutineScope { // Creates a coroutine scope
        launch {
            delay(500L)
            println("Task from nested launch")
        }

        delay(100L)
        println("Task from coroutine scope") // This line will be printed before the nested launch
    }

    println("Coroutine scope is over") // This line is not printed until the nested launch completes
}

输出如下

Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over

那,它们到底有什么不同?什么叫释放底层线程用于其他用途?我也没看到它释放啊?!

既然释放了,为什么Coroutine scope is over还是最后打印?!

甚至于为了找出不同的地方,我把代码中的coroutineScope直接改成了runBlocking

Inappropriate blocking method call ——???

运行

结果完全一样(你到底释放了些什么啊??

——————————————————吐槽完毕

 

点开runBlocking函数的定义

Runs a new coroutine and **blocks** the current thread _interruptibly_ until its completion.

 创建一个新的协程,执行代码,在此期间阻塞当前线程

public fun  runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
//...
//启动eventloop
//...
}

方法中传入了context 和 一个……suspend函数对象。

CoroutineScope?原来这俩是同一个东西?

再看看CoroutineScope

By convention, they all wait for all the coroutines inside their block to complete before completing themselves,
thus enforcing the discipline of **structured concurrency**.

 也就是说,coroutineScope会持续阻塞当前线程直到block内的协程全部执行完毕

什么叫做持续阻塞当前线程直到block内的协程全部执行完毕呢?

fun fun0() = runBlocking {
    println("start")
    coroutineScope {
        launch {
            delay(2000L)
            println("2")
            delay(1000L)
        }
        launch {
            delay(1000L)
            println("1")
        }
    }
    println("over")
}

这个coroutineScope中有两个协程,第一个协程一共执行3秒,第二个一共执行1秒,所以over在start后3秒执行(取coroutineScope中运行时间最长的协程)

执行结果为

start
1
2
over

每隔一秒打印一次

 

 

所以,runBlocking和coroutineScope的表现是完全一致的,区别在于runBlocking创建了一个新的协程

 

所以个人推测,官方教程中的那句『阻塞当前线程来等待』与『释放底层线程用于其他用途』可能针对的并不是runBlocking与coroutineScope,而是针对普通方法与suspend方法——即普通方法会阻塞当前线程等待,而suspend方法可以释放底层线程用于其他用途,runBlocking所谓的阻塞,是指阻塞包含runBlocking的线程——此例中若在主线程运行的话即指main,阻塞着main线程直到runBlocking内的所有协程执行完毕

 

那么suspend又是什么?

凯哥的教程说得很明白了(b站搜索 扔物线 ,协程相关的几个视频)

suspend是一个可能会耗时的提示,常用的大多数suspend函数都带有一个自动切回线程的功能

suspend函数仅在协程内才可运行——因为它们需要协程的上下文来了解如何进行切换线程

 

好了,新的问题,suspend的『释放底层线程用于其他用途』,指的是什么?

首先一个基本的概念:kotlin for java的协程实际上本质是线程——还是来自凯哥(快去b站给凯哥三连啊

所以以下代码的运行结果

fun fun0() = runBlocking {
    launch {
        println("launch current thread : ${Thread.currentThread().name}")
    }
    coroutineScope {
        println("coroutineScope current thread : ${Thread.currentThread().name}")
    }
}

两个thread打印的结果都为main

那么,如果其中一个thread阻塞,还能继续打印吗?

 

即如下代码:

fun fun0() = runBlocking {
    launch {
        println("launch current thread : ${Thread.currentThread().name}")
    }
    coroutineScope {
        println("coroutineScope current thread : ${Thread.currentThread().name}")
        Thread.sleep(1000L)
        println("coroutineScope current thread2 : ${Thread.currentThread().name}")
    }
}

很容易验证,在coroutineScope两条都打印完成后,launch才进行打印,且coroutineScope的打印间隔为1秒

协程也不是什么魔法嘛,想阻塞还是随便阻塞的

不是说好的非阻塞式么,这什么破玩意

 

那么再改一改,用delay!

fun fun0() = runBlocking {
    launch {
        println("launch current thread : ${Thread.currentThread().name}")
    }
    coroutineScope {
        println("coroutineScope current thread : ${Thread.currentThread().name}")
        delay(1000L)
        println("coroutineScope current thread2 : ${Thread.currentThread().name}")
    }
}

咦,不阻塞了,launch神奇得插入到了两条打印之间

这就是所谓的『释放底层线程用于其他用途』

神奇的地方在于suspend中的函数,而不是suspend这个关键字

 

那么delay做了什么?——具体的我不太了解,还待进一步学习。猜测这类kotlin提供的suspend函数,对代码进行了"截取"(实际上反编译出的java文件也看到了case类的字样),delay之前的代码正常执行,然后切到其他线程,阻塞那个线程1秒,再切到main线程执行剩下的代码,在这一秒内,main线程是空闲的,可以接收launch中main线程执行的代码,从而使得launch的打印插入到coroutineScope的打印中

 

这样就很简单了,runBlocking与coroutineScope其实并无太大联系,无非是因为coroutineScope的用途是等待block内的协程全部执行完毕才跳转到下一行代码——无论是runBlocking还是coroutineScope,"Coroutine scope is over"这行打印都在这条block之后,所以当然会在block内的打印完成之后执行,因为此时还没执行到下一行代码嘛~

 

想改变执行顺序?把"Task from runBlocking"中的delay改为大于500L的任意数值,这行代码就会在over之后输出了(coroutineScope中代码的执行时间为500L,执行完毕后就继续执行下一行代码——输出over)

 

所以总结如下:

coroutineScope保证block内所有协程都执行完毕后才可执行下一行代码,宏观来看,实际上官方的例子是这样的

fun fun0() = runBlocking {
    coroutineScope(block())
    println("over") // This line is not printed until the nested launch completes
}

fun block() : suspend CoroutineScope.() -> Unit = {
    println("Thread sleep 1000ms.")
    Thread.sleep(1000L)
}

over当然在Thread sleep后一秒执行啊,有什么可疑问的呢?

runBlocking,实际就是个创建协程的coroutineScope

 

至于为什么launch看起来没有阻塞……因为launch也开了新的协程呀

Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a [Job].

 

以下为胡言乱语,多数为类比式的推测,仅为个人想法记录,未进行任何验证,待后续学习

推测1:event loop与android handler消息机制类似?

推测2:launch等函数实际与handler post等类似?

推测3:suspend实际为将suspend内的block进行拆分,经过指定线程运行后再post到指定线程中?

 

你可能感兴趣的:(有关kotlin中的runBlocking和coroutineScope)