协程的作用
官方描述:协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。kotlin 官方中文详细文档
协程最大的作用就是切换线程。Rxjava也可以线程切换,所以协程与Rxjava切换线程类似的,Rxjava切换是线程,而Kotlin的协程与Rxjava是不同的,协程和线程又有不同的,一个协程是一块块,每一块都有一个上下文Context,通过上下文做桥接来切换。
比如:有A线程和B线程,有三个代码块C1、C2、C3,这些代码块在执行时,在那个线程(A线程或B线程)上切换呢,那么就由协程去控制了,如A线程执行C1、C2代码块,B线程执行C3代码块。执行的单元以协程上下文Context,这么一块一块为单位来切换的。
可以简单地理解为,协程封装好一块块的代码在线程上执行,如封装好的一块块执行单元C1、C2在A线程上,如果要切换到B线程上,那么C1或C2的结果就以条件参数方式传入到C3中在B线程上执行。
协程的使用
-
添加依赖
//协程相关的核心库 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
-
创建协程
在 kotlin 里面提供了大量的高阶函数,kotlin 中 GlobalScope 类提供了几个携程构造函数。
-
runBlocking - 不是 GlobalScope 的 API,可以独立使用,区别是 runBlocking 里面的 delay 会阻塞线程,而 launch 创建的不会。
-
launch- 创建协程。
-
async - 创建带返回值的协程,返回的是 Deferred 类。
-
withContext - 不创建新的协程,在指定协程上运行代码块。
kotlin 在 1.3 之后要求协程必须由 CoroutineScope 创建,CoroutineScope 不阻塞当前线程,在后台创建一个新协程,也可以指定协程调度器。
-
-
runBlocking:T
首先上代码
class MainActivity : AppCompatActivity(){ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) tv_name.text = "learning kotlin" Log.e("lu","主线程——${Thread.currentThread().name}_id_${Thread.currentThread().id}") test() Log.e("lu","协程执行结束。") } private fun test() = runBlocking { //循环10次 repeat(10){ Log.e("lu","协程执行$it——${Thread.currentThread().name}_id_${Thread.currentThread().id}") //挂起2秒 delay(2000) } } }
打印的结果 E/lu: 主线程——main_id_1 E/lu: 协程执行0——main_id_1 E/lu: 协程执行1——main_id_1 E/lu: 协程执行2——main_id_1 E/lu: 协程执行3——main_id_1 E/lu: 协程执行4——main_id_1 E/lu: 协程执行5——main_id_1 E/lu: 协程执行6——main_id_1 E/lu: 协程执行7——main_id_1 E/lu: 协程执行8——main_id_1 E/lu: 协程执行9——main_id_1 E/lu: 协程执行结束。
得出结论:
runBlocking
启动的协程任务会阻断当前线程,直到该协程执行结束。当协程执行结束之后,页面才会被显示出来。 -
launch:Job
首先代码
class MainActivity : AppCompatActivity(){ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) tv_name.text = "learning kotlin" Log.e("lu","主线程——${Thread.currentThread().name}_id_${Thread.currentThread().id}") // test() var job = GlobalScope.launch { //挂起5秒 delay(5_000) Log.e("lu","协程执行——${Thread.currentThread().name}_id_${Thread.currentThread().id}") } Log.e("lu","主线程执行结束。") //另一种写法 指定调度器 Log.e("lu","主线程——${Thread.currentThread().name}_id_${Thread.currentThread().id}") // test() var job = GlobalScope.launch(Dispatchers.Unconfined) { //挂起5秒 delay(5_000) Log.e("lu","协程执行——${Thread.currentThread().name}_id_${Thread.currentThread().id}") } Log.e("lu","主线程执行结束。") } }
打印的结果 E/lu: 主线程——main_id_1 E/lu: 主线程执行结束。 E/GED: Failed to get GED Log Buf, err(0) E/lu: 协程执行——DefaultDispatcher-worker-1_id_2664
launch:Job是最常用的用于启动协程的方式,它最终返回一个Job类型的对象,这个对象实际上是一个接口,它包涵了许多我们常用的函数和变量。
//Job中的常用方法 job.isActive//是否激活 job.isCancelled//是否取消 job.isCompleted//是否完成 job.start()//启动协程,除了LAZY模式,协程都不需要手动启动 job.cancel()//调用取消函数,取消一个协程 job.join()//阻塞,直到某个协程执行完毕 job.cancelAndJoin()//等待协程执行完毕,然后再取消
得出结论
从执行结果看出,
launch
不会阻断主线程。
-
async
首先上代码
class MainActivity : AppCompatActivity(){ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) tv_name.text = "learning kotlin" GlobalScope.launch { var result1 = GlobalScope.async { getResult1() } var result2 = GlobalScope.async { getResult2() } var result = result1.await()+result2.await(); Log.e("lu","result——${result}") } } private suspend fun getResult1():String{ delay(3_000) return "返回结果1" } private suspend fun getResult2():String{ delay(3_000) return "返回结果2" } }
打印的结果 E/lu: result——返回结果1返回结果2 E/lu: 执行时间 3 秒
async跟launch的用法基本一样,区别在于:async的返回值类型是Deferred<T>,将最后一个封装成了该对象。async可以支持并发,此时一般都跟await一起使用。
得出结论
async是不阻塞线程的,也就是说
getResult1
和getResult2
是同时进行的,所以获取到result
的时间是3s,而不是6s。async 返回的是 Deferred 类型,Deferred 继承自 Job 接口,Job有的它都有,增加了一个方法 await ,这个方法接收的是 async 闭包中返回的值,async 的特点是不会阻塞当前线程,但会阻塞所在协程,也就是挂起。
注意:async 并不会阻塞线程,只是阻塞锁调用的协程。
从源码分析GlobalScope.launch和GlobalScope.async
从源码层面去分析函数的使用,这样更加让我们清楚了解其原理。
看源码并分析GlobalScope.launch
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(...)
是CoroutineScope的一个扩展函数,CoroutineScope简单来说就是协程的作用范围,CoroutineScope里定义了很多函数和变量。函数launch需要传入三个参数,返回一个Job对象,Job对象就是一个接口,也继续了CoroutineContext.Element(协程上下文元素),这些元素就是一些变量和函数,方便管理协程的一些动作,如isActive是否激活、isCancelled//是否取消、isCompleted//是否完成、cancel()//调用取消函数等等。
launch
方法有三个参数:
-
协程下上文:context: CoroutineContext,使用
EmptyCoroutineContext
。 -
协程启动模式: start: CoroutineStart,使用
CoroutineStart.DEFAULT
。 -
闭包方法体(协程体):suspend CoroutineScope.() -> Unit。定义协程内需要执行的操作,接收者是CoroutineScope。
注意:前面2个是常规参数,最后一个是个对象式函数,可以使用以kotlin的闭包的写法:() 里面写常规参数,{} 里面写函数式对象的实现
var job = GlobalScope.launch(Dispatchers.Unconfined) {}
var job = GlobalScope.launch {}
看源码并分析GlobalScope.async
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
从方法定义中可以看出,async(...)
函数和launch(...)
函数是一样,只有两个点不同,在launch()函数参数中block闭包方法体是一个Unit(无返回类型)的CoroutineScoper接收者,而要async()函数参数中block闭包方法体是一个返回类型T的CoroutineScope接收者。launch(...)函数的返回值类型是Job对象,而async(...)函数的返回值类型是Deferred<T>,但Deferred<T>是继续了Job接口,所以Deferred<T>只是对Job接口的扩展而已。
协程上下文CoroutineContext
在launch(...)和async(...)函数的函数式对象实现里创建(newCoroutineContext)了一个协程的上下文,使用了默认的协程调度器Dispatchers.Default。
@ExperimentalCoroutinesApi
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = coroutineContext + context
val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
debug + Dispatchers.Default else debug
}
协程上下文(CoroutineContext)有很多作用,包括携带参数,拦截协程执行等等,多数情况下我 们不需要自己去实现协程上下文,只需要使用现成的就好。协程的上下文有一个重要的作用就是线程切换,使用协程调度器CoroutineDispatcher 来确定哪些线程用于协程执行。设置CoroutineDispatcher协程运行的线程调度器,有 4种线程模式:
-
Dispatchers.Main:使用这个调度器在 Android 主线程上运行一个协程。可以用来更新UI 。在UI线程中执行
-
Dispatchers.IO:这个调度器被优化在主线程之外执行磁盘或网络 I/O。在线程池中执行
-
Dispatchers.Default:这个调度器经过优化,可以在主线程之外执行 cpu 密集型的工作。例如对列表进行排序和解析 JSON。在线程池中执行。
-
Dispatchers.Unconfined:在调用的线程直接执行(没有指定,就是在当前线程执行)。
协程调度器CoroutineDispatcher ,可以指定默认的调度器,也可以不指定(不指定就是默认 Dispatchers.Default 模式),或者我们可以自己创建协程上下文,也就是线程池,newSingleThreadContext 单线程。
//不指定调度器
GlobalScope.launch {}
//指定调度器
GlobalScope.launch(Dispatchers.Unconfined) {}
//自定义一个协程的上下文
val singleThreadContext = newSingleThreadContext("嘿嘿,我是单线程")
GlobalScope.launch(singleThreadContext) { ... }
协程启动模式CoroutineStart
直击源码:
public enum class CoroutineStart {
DEFAULT,
LAZY,
@ExperimentalCoroutinesApi
ATOMIC,
@ExperimentalCoroutinesApi
UNDISPATCHED;
//...省略部分代码
}
四种启动模式的作用:
-
DEFAULT——默认的模式,马上执行协程体。
-
LAZY——懒加载模式,只有在需要的情况下才执行。
-
ATOMIC——马上执行协程体,但在开始执行之前无法取消。
-
UNDISPATCHED——马上执行当前线程协程体,直到第一个supend调用。
实现一个LAZY启动模式实例:
var job = GlobalScope.launch(start = CoroutineStart.LAZY) {
//todo 要做的事情
Log.e("lu","协程执行时间:"+System.currentTimeMillis())
}
//休眠5秒,再往下走
Thread.sleep(5_000)
//手动启动协程
job.start()
关于Job的一些函数和变量,协程使用的launch:Job中有介绍就不过多的介绍了。
从上面的源码中可以反映出协程的组成包括的那些要素。
协程的组成
-
CoroutineName(指定协程名称)
-
Job(协程的生命周期,用于取消协程)
-
CoroutineDispatcher,可以指定协程运行的线程
有了CoroutineScope之后可以通过一系列的Coroutine builders来启动协程,协程运行在Coroutine builders的代码块里面。协程的运行代码一定要跑在CoroutineScope里面。
启动一个协程需要CoroutineScope,为什么需要?
CoroutineScope接受CoroutineContext作为参数,CoroutineContext由一组协程的配置参数组成,可以指定协程的名称,协程运行所在线程,异常处理等等。可以通过plus操作符来组合这些参数。上面的代码指定了协程运行在主线程中,并且提供了一个Job,可用于取消协程。
-
launch 启动一个协程,返回一个Job,可用来取消协程;有异常直接抛出
-
async 启动一个带返回结果的协程,可以通过Deferred.await()获取结果;有异常并不会直接抛出,只会在调用 await 的时候抛出
-
withContext 启动一个协程,传入CoroutineContext改变协程运行的上下文。
总结
-
协程的简单使用,想了解更加详细的请到kotlin 官方中文详细文档
-
从例子到源码去了解协程的使用
-
了解协程的结构
网友评论