协程之launch
协程需要依赖以下包
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
}
launch
launch必须放在协程里才行,launch开启的协程不会阻塞上下文线程,如下:
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 在后台启动一个新的协程并继续
println("${Thread.currentThread().name}")
delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
println("World!") // 在延迟后打印输出
}
println("${Thread.currentThread().name}")
println("Hello,") // 协程已在等待时主线程还在继续
Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}
结果:
main
Hello,
DefaultDispatcher-worker-1 @coroutine#1
World!
GlobalScope.launch
GlobalScope.launch可以在代码的任何地方运行,它会在新的工作线程上执行,如上述例子,新的线程名称为:DefaultDispatcher-worker-1 @coroutine#1 。GlobalScope.launch会创建一个全局的线程池,所以多个GlobalScope.launch调用不一定会创建多个线程,如果有空闲线程,则会复用空闲线程。如下所示例子:
GlobalScope.launch {
GlobalScope.launch {
for (i in 1..10){
delay(1000)
println("${Thread.currentThread().name} 2")
}
}
for (i in 1..10){
forTest(1000)
}
}
println("${Thread.currentThread().name} 4")
private suspend fun forTest(time:Long){
delay(1000)
println("${Thread.currentThread().name} 3")
}
结果
2021-06-12 18:20:34.757 22096-22096/? I/System.out: main 4
2021-06-12 18:20:35.767 22096-22759/? I/System.out: DefaultDispatcher-worker-1 2
2021-06-12 18:20:35.767 22096-22760/? I/System.out: DefaultDispatcher-worker-2 3
2021-06-12 18:20:36.770 22096-22760/? I/System.out: DefaultDispatcher-worker-2 2
2021-06-12 18:20:36.771 22096-22760/? I/System.out: DefaultDispatcher-worker-2 3
2021-06-12 18:20:37.773 22096-22759/? I/System.out: DefaultDispatcher-worker-1 2
2021-06-12 18:20:37.776 22096-22759/? I/System.out: DefaultDispatcher-worker-1 3
2021-06-12 18:20:38.778 22096-22760/? I/System.out: DefaultDispatcher-worker-2 2
2021-06-12 18:20:38.779 22096-22760/? I/System.out: DefaultDispatcher-worker-2 3
2021-06-12 18:20:39.782 22096-22759/? I/System.out: DefaultDispatcher-worker-1 2
2021-06-12 18:20:39.783 22096-22759/? I/System.out: DefaultDispatcher-worker-1 3
2021-06-12 18:20:40.786 22096-22760/? I/System.out: DefaultDispatcher-worker-2 2
2021-06-12 18:20:40.788 22096-22760/? I/System.out: DefaultDispatcher-worker-2 3
2021-06-12 18:20:41.789 22096-22760/? I/System.out: DefaultDispatcher-worker-2 2
2021-06-12 18:20:41.791 22096-22759/? I/System.out: DefaultDispatcher-worker-1 3
2021-06-12 18:20:42.793 22096-22760/? I/System.out: DefaultDispatcher-worker-2 2
2021-06-12 18:20:42.795 22096-22760/? I/System.out: DefaultDispatcher-worker-2 3
2021-06-12 18:20:43.796 22096-22759/? I/System.out: DefaultDispatcher-worker-1 2
2021-06-12 18:20:43.798 22096-22759/? I/System.out: DefaultDispatcher-worker-1 3
2021-06-12 18:20:44.798 22096-22760/? I/System.out: DefaultDispatcher-worker-2 2
2021-06-12 18:20:44.799 22096-22760/? I/System.out: DefaultDispatcher-worker-2 3
可以看出,DefaultDispatcher-worker-1
和DefaultDispatcher-worker-2
有时候会交替执行两个不同协程里面的内容。
那么,如果在GlobalScope.launch
中直接调用launch
呢?此时内部launch
是属于谁的launch
?答案是,属于GlobalScope
的launch
,示例如下:
GlobalScope.launch {
launch {
for (i in 1..10){
delay(1000)
println("${Thread.currentThread().name} 2")
}
}
for (i in 1..10){
forTest(1000)
}
}
println("${Thread.currentThread().name} 4")
private suspend fun forTest(time:Long){
delay(1000)
println("${Thread.currentThread().name} 3")
}
结果与上述结果一样,DefaultDispatcher-worker-1
和DefaultDispatcher-worker-2
有时候会交替执行两个不同协程里面的内容。其实可以通过launch
的源码看出来,launch
是属于调用它的scope
,且内层的launch
跟外层的launch
共用同一个scope
类型
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
viewModelScope.launch
viewModelScope.launch
必须在ViewModel
中执行,否则会报错。默认是在主线程上执行,如下所示例子:
class MainViewModel: ViewModel() {
fun testViewModel(){
println("${Thread.currentThread().name} 1")
viewModelScope.launch {
println("${Thread.currentThread().name} 2")
forTest()
}
println("${Thread.currentThread().name} 4")
}
private suspend fun forTest(){
delay(100)
println("${Thread.currentThread().name} 3")
}
}
结果
2021-06-12 17:35:24.101 4370-4370/? I/System.out: main 1
2021-06-12 17:35:24.130 4370-4370/? I/System.out: main 2
2021-06-12 17:35:24.132 4370-4370/? I/System.out: main 4
2021-06-12 17:35:24.233 4370-4370/? I/System.out: main 3
可以看出,launch
不会阻塞当前的线程,println4
在println3
之前执行
viewModelScope.launch
不管其上下文是否是主线程,未指定Dispathers
情况下,默认是在主线程中执行,如下例子:
fun testViewModel(){
println("${Thread.currentThread().name} 1")
viewModelScope.launch(Dispatchers.Main) {
withContext(Dispatchers.IO){
viewModelScope.launch {
delay(1000)
println("${Thread.currentThread().name} 2")
}
println("${Thread.currentThread().name} 3")
}
println("${Thread.currentThread().name} 4")
forTest()
}
println("${Thread.currentThread().name} 6")
}
private suspend fun forTest(){
delay(1000)
println("${Thread.currentThread().name} 5")
}
结果
2021-06-12 18:03:52.768 20168-20168/? I/System.out: main 1
2021-06-12 18:03:52.803 20168-20168/? I/System.out: main 6
2021-06-12 18:03:52.825 20168-20976/? I/System.out: DefaultDispatcher-worker-1 3
2021-06-12 18:03:52.827 20168-20168/? I/System.out: main 4
2021-06-12 18:03:53.830 20168-20168/? I/System.out: main 2
2021-06-12 18:03:53.833 20168-20168/? I/System.out: main 5
可以看到,第二个默认的viewModelScope.launch
,其上下文线程是DefaultDispatcher-worker-1
,自身协程是在主线程中运行的。
Dispathers
Dispatchers
的调度器分别有Default
, Main
, Unconfined
,IO
。launch
可以指定协程运行所在线程,如下:
btn.setOnClickListener {
GlobalScope.launch(Dispatchers.Main) {
println("${Thread.currentThread().name} 1")
forTest()
}
println("${Thread.currentThread().name} 3")
}
private suspend fun forTest(){
delay(1000)
println("${Thread.currentThread().name} 2")
}
结果
2021-06-12 17:51:56.733 19809-19809/? I/System.out: main 3
2021-06-12 17:51:56.738 19809-19809/? I/System.out: main 1
2021-06-12 17:51:57.744 19809-19809/? I/System.out: main 2
我们知道,GlobalScope.launch
默认情况下是会单独开启一个工作线程来执行协程里的内容,在这里,我们通过Dispatchers.Main
指定了GlobalScope.launch
协程在主线程上执行,打印结果确实是在主线程上。
总结
viewModelScope.launch
必须在viewModel
中执行,且未指定运行线程情况下,默认是在主线程中执行,且不会阻塞当前上下文线程GlobalScope.launch
可以在任何地方执行,在未指定运行线程的情况下,默认会开启新的工作线程来执行协程,且不会阻塞当前上下文线程,它底层是一个全局的线程池,多个GlobalScope.launch
可能会使用同一个线程来执行协程。launch
必须在协程中launch
可以通过Dispathers
来指定协程所运行的线程