官方描述html
协程经过将复杂性放入库来简化异步编程。程序的逻辑能够在协程中顺序地表达,而底层库会为咱们解决其异步性。该库能够将用户代码的相关部分包装为回调、订阅相关事件、在不一样线程(甚至不一样机器)上调度执行,而代码则保持如同顺序执行同样简单。android
人话版编程
协程就像很是轻量级的线程。promise
协程是运行在单线程中的并发程序,避免了多线程并发机制中切换线程时带来的线程上下文切换、线程状态切换、线程初始化上的性能损耗,能大幅度唐提升并发性能bash
线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,可是协程挂起时不须要阻塞线程,几乎是无代价的,协程是由开发者控制的。因此协程也像用户态的线程,很是轻量级,一个线程中能够建立任意个协程。多线程
协程是跑在线程上的,一个线程能够同时跑多个协程,每个协程则表明一个耗时任务,咱们手动控制多个协程之间的运行、切换,决定谁何时挂起,何时运行,何时唤醒,而不是线程那样交给系统内核来操做去竞争CPU时间片,缺点是本质是个单线程,不能利用到单个CPU的多个核闭包
添加依赖kotlinx.coroutines并发
kotlinx.coroutines 是由 JetBrains 开发的功能丰富的协程库。它包含本指南中涵盖的不少启用高级协程的原语,包括 launch、 async 等等。异步
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
}
复制代码
固然你确定须要引入kotlin的async
buildscript {
ext.kotlin_version = '1.3.61'
}
repository {
jcenter()
}
复制代码
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
println("World!") // 在延迟后打印输出
}
println("Hello,") // 协程已在等待时主线程还在继续
Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}
复制代码
delay 是一个特殊的 挂起函数 ,它不会形成线程阻塞,可是会 挂起 协程,而且只能在协程中使用。
调用了runBlocking 的线程会一直阻塞直到 runBlocking 内部的协程执行完毕。
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> { // 开始执行主协程
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L)
println("World!")
}
println("Hello,") // 主协程在这里会当即执行
delay(2000L) // 延迟 2 秒来保证 JVM 存活
}
复制代码
这里的 runBlocking { …… } 做为用来启动顶层主协程的适配器。 咱们显式指定了其返回类型 Unit,由于在 Kotlin 中 main 函数必须返回 Unit 类型。
async 建立带返回值的协程,返回的是 Deferred 类
async 同 launch 区别就是 async 是有返回值的 async 返回的是 Deferred 类型,Deferred 继承自 Job 接口,Job有的它都有,增长了一个方法 await ,这个方法接收的是 async 闭包中返回的值,async 的特色是不会阻塞当前线程,但会阻塞所在协程,也就是挂起
withContext 不建立新的协程,在指定协程上运行代码块
runBlocking 不是 GlobalScope 的 API,能够独立使用,区别是 runBlocking 里面的 delay 会阻塞线程,而 launch 建立的不会
协程很轻量
import kotlinx.coroutines.*
fun main() = runBlocking {
repeat(100_000) { // 启动大量的协程
launch {
delay(1000L)
print(".")
}
}
}
复制代码
它启动了 10 万个协程,而且在一秒钟后,每一个协程都输出一个点。 如今,尝试使用线程来实现。会发生什么?(极可能你的代码会产生某种内存不足的错误)
launch 建立协程, launch是个扩展函数,接受3个参数,前面2个是常规参数,最后一个是个对象式函数
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
}
复制代码
launch 的3个参数和返回值
CoroutineContext
能够理解为协程的上下文,在这里咱们能够设置 CoroutineDispatcher 协程运行的线程调度器,有 4种线程模式:
Dispatchers.Default
Dispatchers.IO 子线程
Dispatchers.Main 主线程
Dispatchers.Unconfined 没指定,就是在当前线程
不写的话就是 Dispatchers.Default 模式的,或者咱们能够本身建立协程上下文,也就是线程池
//newSingleThreadContext 单线程,newFixedThreadPoolContext 线程池
val singleThreadContext = newSingleThreadContext("work")
GlobalScope.launch(singleThreadContext) {
}
复制代码
CoroutineStart
启动模式,默认是DEAFAULT,也就是建立就启动;还有一个是LAZY,意思是等你须要它的时候,再调用启动
val lazyStart = GlobalScope.launch(start = CoroutineStart.LAZY) {
RLogUtil.i("启动了")
}
delay(1000)
lazyStart.start()
复制代码
block
闭包方法体,定义协程内须要执行的操做
返回值
协程构建函数的返回值,能够把返回值job当作协程对象自己,协程的操做方法都在job身上了
job.start() - 启动协程,除了 lazy 模式,协程都不须要手动启动
job.join() - 等待协程执行完毕
job.cancel() - 取消一个协程
job.cancelAndJoin() - 等待协程执行完毕而后再取消
suspend 修饰符
当你对这段代码执行“提取函数”重构时,你会获得一个带有 suspend 修饰符的新函数。 那是你的第一个挂起函数
在协程中表现为标记、切换方法、代码段,协程里使用suspend关键字修饰方法,既该方法能够被协程挂起,没用suspend修饰的方法不能参与协程任务,suspend修饰的方法只能在协程中只能与另外一个suspend修饰的方法使用
// 这是你的第一个挂起函数
suspend fun doWorld() {
delay(1000L)
println("World!")
}
复制代码
使用job.cancel()取消,也可使用job.cancelAndJoin() - 等待协程执行完毕而后再取消
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // 延迟一段时间
println("main: I'm tired of waiting!")
job.cancel() // 取消该做业
job.join() // 等待做业执行结束
println("main: Now I can quit.")
}
复制代码
取消是协做的这里须要注意的是一段协程代码必须协做才能被取消。 全部 kotlinx.coroutines 中的挂起函数都是 可被取消的 。它们检查协程的取消, 并在取消时抛出CancellationException。然而,若是协程正在执行计算任务,而且没有检查取消的话,那么它是不能被取消的
import kotlinx.coroutines.*
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) { // 一个执行计算的循环,只是为了占用 CPU
// 每秒打印消息两次
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // 等待一段时间
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消一个做业而且等待它结束
println("main: Now I can quit.")
}
复制代码
job: I'm sleeping 0 ... job: I'm sleeping 1 ...
job: I'm sleeping 2 ... main: I'm tired of waiting!
job: I'm sleeping 3 ... job: I'm sleeping 4 ...
main: Now I can quit.
复制代码
咱们有两种方法来使执行计算的代码能够被取消。第一种方法是按期调用挂起函数来检查取消。对于这种目的 yield 是一个好的选择。 另外一种方法是显式的检查取消状态
将前一个示例中的 while (i < 5) 替换为 while (isActive) 并从新运行它。结果显示是能够取消的
咱们一般使用以下的方法处理在被取消时抛出 CancellationException 的可被取消的挂起函数。好比说,try {……} finally {……} 表达式以及 Kotlin 的 use 函数通常在协程被取消的时候执行它们的终结动做
在实践中绝大多数取消一个协程的理由是它有可能超时。 当你手动追踪一个相关 Job 的引用并启动了一个单独的协程在延迟后取消追踪,这里已经准备好使用 withTimeout 函数来作这件事。
withTimeout 抛出了 TimeoutCancellationException,它是 CancellationException 的子类 来看看示例代码:
import kotlinx.coroutines.*
fun main() = runBlocking {
withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
}
复制代码
在概念上,async 就相似于 launch。它启动了一个单独的协程,这是一个轻量级的线程并与其它全部的协程一块儿并发的工做。不一样之处在于 launch 返回一个 Job 而且不附带任何结果值,而 async 返回一个 Deferred —— 一个轻量级的非阻塞 future, 这表明了一个将会在稍后提供结果的 promise。你可使用 .await() 在一个延期的值上获得它的最终结果, 可是 Deferred 也是一个 Job,因此若是须要的话,你能够取消它。
import kotlinx.coroutines.*
import kotlin.system.*
fun main() = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // 假设咱们在这里作了些有用的事
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // 假设咱们在这里也作了些有用的事
return 29
}
复制代码
她们之间是会同时执行doSomethingUsefulOne()和doSomethingUsefulTwo()不使用的话则是按顺序执行
惰性启动的 async
可选的,async 能够经过将 start 参数设置为 CoroutineStart.LAZY 而变为惰性的。 在这个模式下,只有结果经过 await 获取的时候协程才会启动,或者在 Job 的 start 函数调用的时候。
上述内容也只是带大家熟悉一下协程,学习协程可能会费些时间,协程和线程相似可是和线程有很大区别,特别须要注意一下协程内的使用顺序,协程是很轻量级的,很是适合作一些并发任务,协程仍是有一些高级操做的,如异步流点(看着有点像RxJava)熟练了协程以后基本就能够替代RxJava,协程三连走起!
你们看到这里也基本熟悉了kotlin协程的使用了,本文大多数内容源自于kotlin官网,关于协程仍是一些高级用法,我们下期再见!
感谢