前言
记住前面所说的协程思维模型,我们来看看如何启动一个真实存在的协程。
正文
我们知道启动一个线程,只需要new Thread
,然后调用其start()
方法,便会在操作系统层面创建一个线程来执行其实现的run()
方法中代码。
但是协程并没有一个类让我们来new或者继承,而是通过几个API来启动一个协程,所以这也给我们对比线程这种真实存在的东西有更抽象一点。
launch启动协程
话不多说,我们直接看下面代码:
fun main() {
GlobalScope.launch {
println("Hello World! 1")
delay(1000L)
println("Hello World! 2")
}
println("Hello World! 3")
Thread.sleep(2000L)
}
这里通过launch
和后面的lambda
其实就启动了一个协程,而且这个协程已经在运行了,或者可以这样说,lambda
代码块即是协程运行的代码块,也可以理解为这段lambda
代码就协程。
注意和线程概念一样,协程是一段程序,是一个动态执行的代码范围,比如上面lambda
中的3行代码就是一个协程,在这个lambda
中可以打印出协程的信息,当代码执行完后,协程也就结束了。
从这个角度来看,就更好理解"协程是运行在线程上的Task"这句话了,因为这个协程是运行在主线程上的Task。
非阻塞
还是上面的代码,我们执行结果如下:
我们可以发现虽然协程代码在前面,但是协程代码块后面的打印先执行,这里也就是"非阻塞"的表现。这个效果很像在协程那里开启了一个子线程去执行协程任务,然后主线程可以继续执行,关于具体的实现,我们后面细说。
协程和线程的关系
上面例子代码,我们都会在代码最后调用sleep
休眠了2s,有没有考虑是为什么,我们来把那个sleep
代码给删了:
fun main() {
GlobalScope.launch() {
println("Hello World! 1 ")
delay(1000L)
println("Hello World! 2 ")
}
println("Hello World! 3 ")
}
执行结果如下图所示:
会发现当主线程销毁后,它创建的协程也不会再执行。所以这里sleep
是为了主线程不会这么快退出。
射箭模型
从上面launch
创建的协程执行结果能看的出来,当这个协程被启动后,主线程并没有获取它执行的结果,而模式就类比于射箭,所以launch
创建协程的模式如果构建思维模型的话,可以看成射箭模型。
他们都有的共同点:
- 箭一旦射出去了,目标就无法再改变;协程一旦被
launch
,那么它当中执行的任务也不会被中途改变。 - 箭如果命中了猎物,猎物不会自动送到我们手上来;
launch
的协程一旦任务完成了,即便有结果,也没办法直接返回被调用方。
所以launch
适合的场景是业务去执行且不需要得到其返回值的情况。
launch方法定义
这里并不真的去看launch
的源码实现,而是简单看一下函数参数,简单了解一下,launch
函数定义如下:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
...
}
这里其实很考察Kotlin语法的熟悉程度,我们来简单看一下:
-
launch
是一个扩展函数,它的接收者类型是CoroutineScope
,这个表示协程的作用域,而Kotlin的扩展方法就表明launch
就相当于CoroutineScope
的成员方法。 -
第一个参数是
CoroutineContext
,这个是协程的上下文,默认值就是EmptyCoroutineContext
,而这个参数也可以传递Dispatchers
,来指定协程运行的线程池。 -
第二个参数是
CoroutineStart
,表示协程的启动模式,一般是DEFAULT
表示立即执行,还有就是LAZY
表示懒加载执行。 -
第三个参数是
suspend CoroutineScope.() -> Unit
,这是带接收者的函数类型参数。回忆一下带接收者的函数类型含义:带接收者的函数类型,就相当于该接收者的成员函数,可以调用其接收者的成员方法。我们可以类比Kotlin标准库的
apply
方法,上面block参数为:block: suspend (CoroutineScope) -> Unit
,而这个CoroutineScope
也是我们扩展函数接收者对象。这也就说明在
block
中我们可以随意调用CoroutineScope
中的成员方法。最后就是
suspend
关键字,这个表示是挂起函数,关于挂起函数我们后面细说,现在只需要知道挂起函数编译器会有特殊处理。 -
返回值是
Job
,它代表是协程的句柄,并不能返回协程的执行结果,这也就说明了launch
启动的协程不能返回结果。
再结合block
的类型,是返回Unit
,我们也可以知道launch
所启动的协程,是没有返回结果的,也印证了前面所说的"射箭模型"。
总结
如果本篇文章只需要记住一点,那就是launch
开启的协程就像是"射出去的箭",在启动的地方"射出去",去干一些不需要结果、一劳永逸的任务,不会阻塞接下来程序的执行。
从这篇文章的分析来看,我们终于真真切切地启动了一个协程,而且通过分析launch
的函数定义,来分析了其返回值、带接收者的函数类型参数等,知道了有CoroutineScope
的概念,只能在这个Scope
下才可以用launch
启动协程。