先理清楚三种说法:
1、协程是轻量级线程、比线程耗费资源少
错。竟然还是官方说法。协程是语言层面的东西,而线程是操作系统层面的,就没啥可比性。 kotlin协程,和操作系统概念的协程不一样,理念有一点点像,但是没有任何关系。
2、协程是线程框架
对。就是对线程的封装,模糊了线程。
- 协程中切换线程非常方便,由此解决了异步编程时过多的回调问题。
- Kotlin 协程内部自己维护了线程池。
- 在使用协程过程中,无需关注线程的切换细节,只需指定想要执行的线程类型即可。
这个问题可能不太直观。为便于理解协程的优势,贴一段代码。 代码1的执行线程,和代码4执行的线程,有可能是不一样的。从这个例子,你应该能理解协程的优势了吧? 这就是宣传的“用同步的方式,写异步的代码”的意思。
fun drive() {
GlobalScope.launch(Dispatchers.IO) { //Dispatchers.IO,指定线程类型。后面是线程池。
println("我坐车,我快乐")//1,开启协程1
withContext(Dispatchers.Default) {//2, 开启协程2,并且切换到其他线程
println("导游去订酒店")//3
}
println("车继续开,剩下团友在车上")//4, 再次执行协程1,线程切换到Dispatchers.IO线程池.
}
}
3、协程效率高于线程
错。与第一点类似,协程与线程没有可比性。协程在运行方面的高效率,换成回调方式也是能够达成同样的效果。 协程内部本来就是回调实现的。只是在编译阶段封装了回调的细节而已。
在介绍如何使用前,先介绍一下,协程中几个重要的概念类。
-
CoroutineScope
1. 协程作用域。它决定了线程调度池的范围和生命周期。 2. 开发者可以在作用域里开协程干事情,相当于你创建了一个Task,然后submit给线程调度池。 3. 最后用cancel()方法关闭整个任务调度。类比于线程池的shutdown()。 4. Android kotlin扩展库中,有两个比较好用的协程作用域,viewModelScope和lifecyceScope, 分别针对ViewModel和Activity/Fragment。当退出时,会自动帮你关闭协程作用域里的所有协程。
下面的示例,是开发者创建协程作用域时, 可以遵循的方式。
class ExampleClass {
// 定义作用域,指定默认的协程执行线程。
//Job()下节再讨论。
val scope = CoroutineScope(Job() + Dispatchers.Main)
fun exampleMethod() {
// 用launch在作用域里创建一个协程任务
scope.launch {
// 协程任务执行的内容。
fetchDocs()
}
}
fun cleanUp() {
// 取消作用域,并且会关闭作用域里所有的协程任务。
scope.cancel()
}
}
-
CoroutineContext
协程上下文,用来设置协程属性值,比如,控制协程的生命周期(Job&),指定在哪类线程中执行(Dispatchers),设置协程的名字(CoroutineName), 捕获协程抛出的异常(CoroutineExceptionHandler)等。
它们都有一个共同的特点,都是继承自CoroutineContext,是为了方便用一个链表串起来。CoroutineContext重载了Plus符号,下面的运算符重载函数目的,就是用+号做成一个链表。
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
context.fold(this) { acc, element ->
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext) element else {
// make sure interceptor is always last in the context (and thus is fast to get when present)
val interceptor = removed[ContinuationInterceptor]
if (interceptor == null) CombinedContext(removed, element) else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
下面代码是设置CoroutineContext的范例:
override fun init() {
val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, Throwable ->
}
viewModelScope.launch(SupervisorJob() + Dispatchers.IO + CoroutineName("alan") + coroutineExceptionHandler) {
}
}
- 协程常用函数:contextWith/runBlocking/launch/join/delay/async/await
- runBlocking,在当前线程执行,所以会阻塞当前线程,使得协程执行结束后才会执行runBlocking 后的代码。通常用于一些测试的场景。
- launch,协程启动函数,开启线程执行。launch()函数并不阻塞当前线程
- delay,挂起函数,但是不阻塞线程,因为任务会退到等待队列中去。它的过程如下: 1)构造task 加入到延迟队列里,此时协程挂起。2) 有个单独的线程会检测是否需要取出task并执行,没到时间的话就要挂起等待。 3)时间到了从延迟队列里取出并放入正常的队列,并从正常队列里取出执行。 4)task 执行的过程就是协程恢复的过程
- contextWith,切换线程。协程最核心的函数。
- async/await, launch的不足之处:协程执行没有返回值。然而,在有些场景我们需要返回值,此时轮到async/await 出场了。与launch 启动方式不同的是,async 的协程定义了返回值,是个泛型。并且async里使用的是DeferredCoroutine,顾名思义:延迟给结果的协程。
fun testAsync() { runBlocking { //启动协程 var job = GlobalScope.async { println("job1 start") Thread.sleep(10000) //返回值 "fish" } //等待协程执行结束,并返回协程结果 var result = job.await() println("result:$result") } }
- join,等待协程执行结束。虽然launch()函数不阻塞线程,但是我们就想要知道协程执行完毕没,进而根据结果确定是否继续往下执行,这时候该Job.join()出场了。
- 协程异常处理
和线程一样,协程也会遇到,需要中止和异常中止,这两种。与线程处理方式类似:对于阻塞状态的协程,我们可以捕获异常,对于非阻塞的地方我们使用状态判断。
需要中止,
线程里就是:Thread.Interrupt()/Thread.Interrupted().
相应的协程里就是:job.isCancelled/job.cancel()
//属性
job.isActive //协程是否活跃
job.isCancelled //协程是否被取消
job.isCompleted//协程是否执行完成
...
//函数
job.join()//等待协程完成
job.cancel()//取消协程
job.invokeOnCompletion()//注册协程完成回调
...
fun testCancel5() {
runBlocking() {
var job1 = launch(Dispatchers.IO) {
try {
//挂起函数
} catch (e : Exception) {
println("delay exception:$e")
}
if (!isActive) {
println("cancel")
}
}
}
}
异常中止:
也就是对抛异常的处理。分为在子协程里捕获和全局捕获。
子协程里捕获:try/catch不要加到协程外面,因为主线程不能捕获子线程的异常。
fun testException2() {
runBlocking {
var job1 = launch(Dispatchers.IO) {
try {
println("job1 start")
//异常
1 / 0
println("job1 end")
} catch (e : Exception) {
println("e=$e")
}
}
}
}
全局捕获:
与线程类似,协程也可以全局捕获异常。就是设置我们前面讲到的CoroutineExceptionHandler。虽然能够捕获异常,但是发生异常的协程还是不能往下执行了。
//创建处理异常对象
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("handle exception:$exception")
}
fun testException3() {
runBlocking {
//声明协程作用域
var scope = CoroutineScope(Job() + exceptionHandler)
var job1 = scope.launch(Dispatchers.IO) {
println("job1 start")
//异常
1 / 0
println("job1 end")
}
}
}
SupervisorJob:
关于异常不得不讲一下SupervisorJob。
当默认或者设置Job()的时候。子协程发生异常后,会取消父协程、同级兄弟协程的执行,这在有些场景是不合理的,因为伤害范围太广,明明是一个子协程的锅,非得所有协程来背。
SupervisorJob它的作用,就是谁的锅谁背。
好了,协程的简单讲解就到此了。虽然是简单,但也触及了核心,是理解协程的基础。
引用: