Kotlin语言基础入门到熟悉:Kotlin协程基础_学习

Kotlin语言基础入门到熟悉:Kotlin协程基础

1. 阻塞与非阻塞

runBlocking

delay是非阻塞的,Thread.sleep是阻塞的。显式使用 runBlocking 协程构建器来阻塞。

import kotlinx.coroutines.\*



fun main() {

 GlobalScope.launch { // 在后台启动一个新的协程并继续

 delay(200)

 "rustfisher.com".forEach {

 print(it)

 delay(280)

 }

 }

 println("主线程中的代码会立即执行")

 runBlocking {     // 这个表达式阻塞了主线程

 delay(3000L)  //阻塞主线程防止过快退出

 }

 println("\n示例结束")

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

可以看到,runBlocking里使用了delay来延迟。用了runBlocking的主线程会一直阻塞直到runBlocking内部的协程执行完毕。 也就是runBlocking{ delay }实现了阻塞的效果。

我们也可以用runBlocking来包装主函数。

import kotlinx.coroutines.\*



fun main() = runBlocking {

 delay(100) // 在这里可以用delay了



 GlobalScope.launch {

 delay(100)

 println("Fisher")

 }

 print("Rust ")

 delay(3000)

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

runBlocking<Unit>中的<Unit>目前可以省略。

runBlocking也可用在测试中

// 引入junit

dependencies {

 implementation("junit:junit:4.13.1")

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

单元测试

使用@Test设置测试

import org.junit.Test

import kotlinx.coroutines.\*



class C3Test {



 @Test

 fun test1() = runBlocking {

 println("junit测试开始 ${System.currentTimeMillis()}")

 delay(1234)

 println("junit测试结束 ${System.currentTimeMillis()}")

 }

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

运行结果

junit测试开始 1632401800686

junit测试结束 1632401801928
  • 1.
  • 2.
  • 3.

IDEA可能会提示no tasks available。需要把测试选项改为IDEA,如下图。

更改设置

Kotlin语言基础入门到熟悉:Kotlin协程基础_java_02

2. 等待

有时候需要等待协程执行完毕。可以用join()方法。这个方法会暂停当前的协程,直到执行完毕。需要用main() = runBlocking

import kotlinx.coroutines.\*



fun main() = runBlocking {

 println("测试等待")

 val job1 = GlobalScope.launch {

 println("job1 start")

 delay(300)

 println("job1 done")

 }

 val job2 = GlobalScope.launch {

 println("job2 start")

 delay(800)

 println("job2 done")

 }



 job2.join()

 job1.join() // 等待

 println("测试结束")

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

运行log

测试等待

job1 start

job2 start

job1 done

job2 done

测试结束
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
3. 结构化的并发

GlobalScope.launch时,会创建一个顶层协程。它不使用主线程。新创的协程虽然轻量,但仍会消耗一些内存资源。如果忘记保持对新启动的协程的引用,它还会继续运行。

我们可以在代码中使用结构化并发。

示例中,我们使用runBlocking协程构建器将main函数转换为协程。在里面(作用域)启动的协程不需显式使用join

观察下面的例子:

import kotlinx.coroutines.\*



fun main() = runBlocking<Unit> {

 println("主线程id ${Thread.currentThread().id}")

 launch { // 在 runBlocking 作用域中启动一个新协程1

 println("协程1所在线程id ${Thread.currentThread().id}")

 delay(300)

 println("协程1执行完毕")

 }

 launch { // 在 runBlocking 作用域中启动一个新协程2

 println("协程2所在线程id ${Thread.currentThread().id}")

 delay(500)

 println("协程2执行完毕")

 }

 println("主线程执行完毕")

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

运行log

主线程id 1

主线程执行完毕

协程1所在线程id 1

协程2所在线程id 1

协程1执行完毕

协程2执行完毕
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

可以看到,不用像之前那样调用Thread.sleep或者delay让主线程等待一段时间,防止虚拟机退出。

程序会等待它所有的协程执行完毕,然后真正退出。

4. 作用域构建器

使用 coroutineScope 构建器声明自己的作用域。它会创建一个协程作用域,并且会等待所有已启动子协程执行完毕。

runBlockingcoroutineScope 看起来类似,因为它们都会等待其协程体以及所有子协程结束。主要区别在于:

* runBlocking 方法会阻塞当前线程来等待,是常规函数

* coroutineScope 只是挂起,会释放底层线程用于其他用途,是挂起函数

下面这个示例展示了作用域构建器的特点。main是一个作用域。

import kotlinx.coroutines.\*



fun main() = runBlocking { // this: CoroutineScope

 launch {

 delay(200L)

 println("协程1 t${Thread.currentThread().id}")

 }



 coroutineScope { // 创建一个协程作用域

 launch {

 delay(500L)

 println("内部协程2-1 t${Thread.currentThread().id}")

 }



 delay(100L)

 println("协程2 t${Thread.currentThread().id}")

 }



 println("主任务完毕")

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

运行log

协程2 t1

协程1 t1

内部协程2-1t1

主任务完毕
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
5. 提取函数重构

launch { …… }内部的代码块提取到独立的函数中。提取出来的函数需要 suspend 修饰符,它是挂起函数

import kotlinx.coroutines.delay

import kotlinx.coroutines.launch

import kotlinx.coroutines.runBlocking



fun main() = runBlocking<Unit> {

 launch { r1() }

 println("DONE")

}



// 挂起函数

suspend fun r1() {

 delay(300)

 println("提取出来的函数")

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

log

DONE

提取出来的函数
  • 1.
  • 2.
  • 3.
6. 协程是轻量的

我们前面也试过,创建非常多的协程,程序运行OK。

下面的代码可以输出很多的点

import kotlinx.coroutines.\*



fun main() = runBlocking {

 for (t in 1..10000) {

 launch {

 delay(t \* 500L)

 print(".")

 }

 }

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
7. 全局协程像守护线程

如果进程中只剩下了守护线程,那么虚拟机会退出。 前文那个的例子,其实也能看到,字符没打印完程序就结束了。

GlobalScope 中启动的活动协程并不会使进程保活。它们就像守护线程。

再举一个例子

import kotlinx.coroutines.\*



fun main() = runBlocking {

 GlobalScope.launch {

 for (i in 1..1000000) {

 delay(200)

 println("协程执行: $i")

 }

 }



 delay(1000)

 println("Bye~")

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

log

协程执行: 1

协程执行: 2

协程执行: 3

协程执行: 4

Bye~
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

最后分享一份

【腾讯技术团队出品】Android零基础入门到精通,Android Studio安装教程+全套安卓基础教程

Android编程入门教程

Java语言基础从入门到熟悉

Kotlin语言基础入门到熟悉:Kotlin协程基础_java_03

Kotlin语言基础从入门到熟悉

Kotlin语言基础入门到熟悉:Kotlin协程基础_开发语言_04

Android 技术栈从入门到熟悉

Kotlin语言基础入门到熟悉:Kotlin协程基础_学习_05

Android Jetpack 全家桶全面学习

Kotlin语言基础入门到熟悉:Kotlin协程基础_开发语言_06

对于新手来说可能安装Android Studio存在一定困难你可以看着以下视频,一步步的跟着学习安装运行

Android Studio 安装教程

Kotlin语言基础入门到熟悉:Kotlin协程基础_java_07

有了Java阶段的学习,这一阶段建议以视频学习为主辅以图书查漏补缺。如果以图书为主,可以根据图书讲解敲代码,辅以教学视频查漏补缺。遇到问题可以去百度,入门的问题一般会有很多人遇到,并且给出比较好的解答。

需要掌握基本知识点,比如四大组件如何使用、如何创建Service、如何进行布局、简单的自定义View、动画、网络通信等常见技术。


Kotlin语言基础入门到熟悉:Kotlin协程基础_java_08

Kotlin语言基础入门到熟悉:Kotlin协程基础_kotlin_09

Kotlin语言基础入门到熟悉:Kotlin协程基础_android_10

Kotlin语言基础入门到熟悉:Kotlin协程基础_开发语言_11


Kotlin语言基础入门到熟悉:Kotlin协程基础_android_12


Kotlin语言基础入门到熟悉:Kotlin协程基础_java_13


Kotlin语言基础入门到熟悉:Kotlin协程基础_开发语言_14