协程是什么?
就是同步方式去编写异步执行的代码。协程是依赖于线程,但是协程挂起的时候不需要阻塞线程。几乎没有任何代价。
协程的创建
一个线程可以创建多个协程。协程的创建是通过CoroutineScope创建,协程的启动方式有三种。
- runBlocking: T 启动一个新的协程并阻塞调用它的线程,直到协程里面的代码执行完毕,返回值是范型T,就是协程体中最后一行返回的是什么类型,那么T就是什么类型。
- launch: job 启动一个协程但不会阻塞调用它的线程,必须在协程作用域(CoroutineScope)中才能调用,返回的是一个job。
- async: Deferred<T>启动一个协程但不会阻塞调用它的线程,必须在协程作用域(CoroutineScope)中才能调用。以Deferred对象的形式返回协程任务。返回值范型T同runBlocking类似都是协程体的最后一行。
上述内容提到了Job、Deferred,它们是什么东西呢?
job
job可以认为就是一个协程作业是通过CoroutineScope.launch生成,同时它运行一个指定的代码块,并在该代码块完成时完成。可以通过isActive、isCompleted、isCancelled来获取Job当前状态。
Deferred
Deferred继承job,可以把它看成一个带有返回值的job。
public interface Deferred<out T>: job {
// 返回结果值,或者如果延迟被取消,则抛出响应的异常
public suspend fun await(): T
public val onAwait: SelectClause1<T>
public fun getCompleted(): T
public fun getCompletionExceptionOrNull():Trowable?
}
作用域
协程作用域(CoroutineScope)是协程运行的作用范围。launch、async都是CoroutineScope的扩展函数。CoroutineScope定义了新启动的协程作用域范围,同时会继承了它的CoroutineContext自动传播其所有的elements和取消操作。也就是说,如果这个作用域销毁了,那么里面的协程也就失效了。
协程的基础用法
private fun start(){
val runBlockingJob = runBlocking {
Log.d("runBlocking", "启动一个协程")
41
}
Log.d("runBlockingJob", "$runBlockingJob")
val launchJob = GlobalScope.launch{
Log.d("launch", "启动一个协程")
}
Log.d("launchJob", "$launchJob")
val asyncJob = GlobalScope.async{
Log.d("async", "启动一个协程")
"我是返回值"
}
Log.d("asyncJob", "$asyncJob")
}
输出为:
D/runBlocking: 启动一个协程
D/runBlockingJob: 41
D/launchJob: StandaloneCoroutine{Active}@3b8b871
D/launch: 启动一个协程
D/async: 启动一个协程
D/asyncJob: DeferredCoroutine{Active}@63f2656
或者
D/runBlocking: 启动一个协程
D/runBlockingJob: 41
D/launchJob: StandaloneCoroutine{Active}@1344515
D/asyncJob: DeferredCoroutine{Active}@38c002a
D/async: 启动一个协程
D/launch: 启动一个协程
或者
D/runBlocking: 启动一个协程
D/runBlockingJob: 41
D/launch: 启动一个协程
D/launchJob: StandaloneCoroutine{Active}@b94e973
D/async: 启动一个协程
D/ asyncJob: DeferredCoroutine{Active}@f7aa030
由于runBlocking启动的是一个新的协程并阻塞调用它的线程。因此可以看到runBlocking的相关日志输出的位置是不会变化的。这就是说runBlocking会阻塞调用它的线程,直到runBlocking运行结束才能继续执行下去。
我们看到后面四条日志是无序的,但是launchJob始终在asyncJob前面。而launch和async协程体内的日志输出是无序的。每执行一次看到的顺序都有可能跟之前的不一样。我们前面提到过launch和async都是启动一个协程但不会阻塞调用线程,所以launchJob始终在asyncJob前面。
runBlocking的返回值
由上述的日志可以看到runBlockingJob的输出结果为41,为什么是41呢?其他的默认返回值一个协程作业的当前状态。
最后的返回值是调用了coroutine.joinBlocking()方法。
coroutine.joinBlocking()方法将state 强转为了范型T。
runBlocking它的设计目的就是将常规的阻塞代码连接到一起,主要用于main函数和测试中。
launch函数
上述日志看到launchJob输出的是一个standaLoneCoroutine对象,不是说输出是一个job吗?
可以看到launch方法最终返回的是coroutine对象,最后返回的是standaLoneCoroutine对象,其实standaLoneCoroutine对象就是一个job。
async 函数
asyncJob返回的是DeferredCoroutine对象
asyncJob返回 DeferredCoroutine不仅继承AbstractCoroutine<T>,同样也继承Deferred<T>接口。那么DeferredCoroutine就是一个Deferred<T>,一个携带返回值的job。
我们一开始需要关注的Deferred的await()方法,可以通过返回的Deferred对象,调用await()方法来获取返回值。
public suspend fun awiat(): T
挂起函数
suspend是协程的关键字,表示是一个挂起函数,每一个被suspend修饰的方法只能在suspend方法或者协程中调用。
private fun start(){
GlobalScope.launch{
val launchJob = launch{
Log.d("launch", "启动一个协程")
}
Log.d("launchJob", "$launchJob")
val asyncJob = async{
Log.d("async", "启动一个协程")
"我是async返回值"
}
Log.d("asyncJob.await", ":${asyncJob.await()}")
Log.d("asyncJob", "$asyncJob")
}
}
输出:
D/launchJob: StandaloneCoroutine{Active}@f3d8da3
D/launch: 启动一个协程
D/async: 启动一个协程
D/asyncJob.await:我是async返回值
D/asyncJob: DeferredCoroutine{Completed}@d6f28a0
或者
D/launchJob: StandaloneCoroutine{Active}@f3d8da3
D/async: 启动一个协程
D/launch: 启动一个协程
D/asyncJob.await: :我是async返回值
D/asyncJob: DeferredCoroutine{Completed}@d6f28a0
可以看到asyncJob.await()输出的是我们之前定义好的返回值,同时DeferredCoroutine的状态变成了{Completed}。这是因为await()是在不阻塞线程的情况下等待该值完成并继续执行,当Deferred计算完成后返回结果值,或者deferred被取消,则抛出响应的异常CancellationException。但是又因为await()是挂起函数。他会挂起调用它的协程。因此我们看到Deferred的状态是{Completed}。同时输出的await()日志也在后面。