协程的概念在编程语言的早期就出现了,在1967年Simula第一次使用协程。 协程就像很是轻量级的线程。 线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,可是协程挂起时不须要阻塞线程,几乎是无代价的,协程是由开发者控制的。因此协程也像用户态的线程,很是轻量级,一个线程中能够建立任意个协程。html
举个你妹子都听得懂的栗子,不必定很准确。假如要从 地铁A站 去 地铁C站 和你妹子约会,可是当到达 地铁B站 的时候,你想来想去应该去给妹子买个精美的礼物,大概要1个小时,而从 A站 到 C站 只有这一辆列车,只不过开的飞快,每10分钟又回到 A站从新出发,地铁比如一条线程,你去买礼物回到B站比如一个任务。在同步阻塞的状况下,是你去买礼物这段时间,地铁一直等你,直到你带着礼物回来。在有协程的状况下,你去买礼物比如一段协程,地铁把你在B站放下(挂起),地铁继续往前开,你买好礼物了就在B站等下趟地铁来,上车继续(恢复)前去和妹子约会。在异步的状况下是,你去买礼物(异步任务),地铁继续往前开,可是地铁司机给你一个电话号码(callback),你买礼物回到B站的时候须要打个人电话号码,才让你上车。异步callback的时候有个问题,每一个人下车去临时办事司机还要给他一个电话号码,若是他出异常不回来了,可能会致使司机的电话号码泄露,很是不安全。android
在Android开发中,常常遇到的问题:git
在Android中主线程主要用户UI的渲染和响应用户手势交互,以及轻量级的逻辑运算。若果在主线程发起一个请求,将会致使应用变慢、变卡、没法响应用户的交互,很容易形成ARN,用户体验极差。因此业界通行的作法是经过callback实现异步回调:github
class ViewModel: ViewModel() {
fun fetchDocs() {
get("developer.android.com") { result ->
show(result)
}
}
}
复制代码
上面callback的示例只是一层回调的状况,假若有两个甚至更多的异步请求,并且存在下一个请求依赖上一个请求的结果,就会存在层层嵌套,固然目前比较流行的作法是用Retrofit的转换函数flatMap实现链式调用,可是代码看起来仍是很臃肿。若是使用协程上面的代码能够简化成这样:编程
// Dispatchers.Main
suspend fun fetchDocs() {
// Dispatchers.Main
val result = get("developer.android.com")
// Dispatchers.Main
show(result)
}
suspend fun get(url: String) = withContext(Dispatchers.IO) {
// Make a request
// Dispatchers.IO
}
复制代码
Coroutines提供一个很好途径能够简化耗时任务的代码编写,使得异步callback的代码能够像同步代码同样顺序编写。Coroutines在普通的方法上面加上两个新的操做。除了call 并 return,Coroutines还增长 suspend 和 resume。 协程使用栈帧管理当前运行的方法和方法的全部本地变量。当协程开始挂起,当前栈帧被复制并保存以供后续使用。当协程开始被恢复,栈帧将从它被保存的地方恢复回来,当前栈帧的方法继续执行。api
suspend functions
只能在协程或者suspend functions
中被调用。安全
在Kotlin协程中,写的好的suspend functions
老是应该能够安全的从主线程被调用,也应该容许从任何线程被调用。使用suspend
修饰的 function
并非告诉Kotlin这个方法在主线程运行。bash
为了写一个主线程安全的耗时方法,你可让协程在Default
或者 IO
dispatcher中执行(用withContext(Dispatchers.IO)
指定在IO线程中运行)。在协程全部的协程必须运行在dispatcher中,即便他们运行在主线程中。Coroutines将会挂起本身,dispatcher知道如何恢复他们。网络
为了指定coroutines在什么线程运行,kotlin提供了四种Dispatchers:并发
Dispatchers | 用途 | 使用场景 |
---|---|---|
Dispatchers.Main | 主线程,和UI交互,执行轻量任务 | 1.call suspend functions 。2. call UI functions 。 3. Update LiveData |
Dispatchers.IO | 用于网络请求和文件访问 | 1. Database 。 2.Reading/writing files 。3. Networking |
Dispatchers.Default | CPU密集型任务 | 1. Sorting a list 。 2.Parsing JSON 。 3.DiffUtils |
Dispatchers.Unconfined | 不限制任何制定线程 | 高级调度器,不该该在常规代码里使用 |
假如你在Room中使用
suspend functions
、RxJava
、LiveData
,它自动提供了主线程安全。
// Dispatchers.Main
suspend fun fetchDocs() {
// Dispatchers.Main
val result = get("developer.android.com")
// Dispatchers.Main
show(result)
}
// Dispatchers.Main
suspend fun get(url: String) =
// Dispatchers.IO
withContext(Dispatchers.IO) {
// Dispatchers.IO
/* perform blocking network IO here */
}
// Dispatchers.Main
复制代码
你的程序里面可能会有成千上万个协程,你很难经过代码手动追踪它们,假如你经过代码手动追踪他们以确保它们完成或取消,那么代码会显得臃肿且很容易出错。若是代码不是很完美,可能会失去对coroutine的追踪,并致使任务泄露。任务泄露就像内存泄露,可是更严重。它不但会浪费内存的使用,还有cpu、磁盘,甚至会发起一个网络请求。
在android中,咱们知道Activity和Fragment等都是有生命周期的,咱们一般的模式是当前页面退出的时候,取消全部的异步任务。假若有一个异步的网络请求,在当前页面销毁的时候还在执行,会致使哪些问题:
为了不协程泄露,kotlin引入 结构化并发 。结构化并发是语言特性和最佳实践的组合,若是咱们遵循最佳实践,将帮助追踪运行在协程中的任务。在Android中结构化并发能够帮咱们作以下三件事:
解决方式:
CoroutineScope
取消任务,实际上是经过关联的job取消任务。coroutineScope
和 supervisorScope
保证 suspend function
的全部任务完成才返回。coroutineScope
保证错误双向传递,只要有一个子coroutine失败或出现异常,异常往父域传递,并取消全部的子coroutines。而 supervisorScope
实现单向错误传递,适用于做业监控器。在kotlin中全部的协程必须运行在CoroutineScope中,scope帮你追踪全部协程的状态,可是它不像Dispatcher,并不运行你的协程。它能够取消全部在里面启动的协程。启动一个新协程:
scope.launch {
// This block starts a new coroutine
// "in" the scope.
//
// It can call suspend functions
fetchDocs()
}
复制代码
建立CoroutineScope
的常见方式以下:
CoroutineScope(context: CoroutineContext)
,api方法,如:val scope = CoroutineScope(Dispatchers.Main + Job())
,或者以下:class LifecycleCoroutineScope : CoroutineScope, Closeable {
private val job = JobSupervisorJob()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun close() {
job.cancel()
}
}
复制代码
class SimpleRetrofitActivity : FragmentActivity() {
private val activityScope = LifecycleCoroutineScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_simple_retrofit)
// some other code ...
}
override fun onDestroy() {
super.onDestroy()
activityScope.close()
}
// some other code ...
}
复制代码
coroutineScope
:api方法,建立新一个子域,并管理域中的全部协程。注意这个方法只有在block中建立的全部子协程所有执行完毕后,才会退出。supervisorScope
:与 coroutineScope
的区别是在子协程失败时,错误不会往上传递给父域,因此不会影响子协程。建立协程的常见方式以下:
lauch
:协程构建器,建立并启动(也能够延时启动)一个协程,返回一个Job,用于监督和取消任务,用于无返回值的场景。async
:协程构建器,和launch同样,区别是返回一个Job的子类 Deferred
,惟一的区别是能够经过await获取完成时的返回值,或者捕获异常(异常处理也不同)。在Android中有一个kotlin的ViewModel的扩展库 lifecycle-viewmodel-ktx:2.1.0-alpha04
,能够经过viewModelScope
扩展属性启动协程,viewModelScope
绑定了activity的生命周期,activity销毁的时候会自动取消在这个scope中启动的全部协程。
fun runForever() {
// start a new coroutine in the ViewModel
viewModelScope.launch {
// cancelled when the ViewModel is cleared
while(true) {
delay(1_000)
// do something every second
}
}
}
复制代码
注意,协程的取消是协做的,当协程挂起的时候被取消将会抛一个
CancellationException
,即便你捕获了这个异常,这个协程的状态也变为取消状态。假如你是一个计算协程,而且没有检查取消状态,那么这个协程不能被取消。
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) { // computation loop, just wastes CPU
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
复制代码
有时,咱们但愿两个或多个请求同时并发,并等待他们所有完成,suspend function
加上 coroutineScope
建立的子域能够保证所有子协程完成才返回。
suspend fun fetchTwoDocs() {
coroutineScope {
launch { fetchDoc(1) }
async { fetchDoc(2) }
}
}
复制代码
注意协程的结构化并发是基于语言特性加上最佳实践的,以下方式会致使,错误丢失:
val unrelatedScope = MainScope()
// example of a lost error
suspend fun lostError() {
// async without structured concurrency
unrelatedScope.async {
throw InAsyncNoOneCanHearYou("except")
}
}
复制代码
上面代码丢失错误是由于 async
的恢复须要调用await
,这样才能将异常从新上传,而在suspend function
使用了另一个协程域,致使lostError
不会等待自做业的完成就退出了。正确的结构化并发:
suspend fun foundError() {
coroutineScope {
async {
throw StructuredConcurrencyWill("throw")
}
}
}
复制代码
你能够经过
CoroutineScope
(注意是大写开头的C
) 和GlobalScope
来建立 非结构化的协程,仅仅当你认为它的生命周期比调用者生命周期更长。
CoroutineScope
:协程做用域包含 CoroutineContext
,用于启动协程,并追踪子协程,实际上是经过Job追踪的。CoroutineContext
:协程上下文,主要包含Job
和CoroutineDispatcher
,表示一个协程的场景。CoroutineDispatcher
:协程调度器,决定协程所在的线程或线程池。它能够指定协程运行于特定的一个线程、一个线程池或者不指定任何线程。Job
:任务,封装了协程中须要执行的代码逻辑。Job 能够取消而且有简单生命周期,它有三种状态:isActive
、isCompleted
、isCancelled
。Deferred
:Job的子类,有返回值的Job,经过await
获取。lauch
、async
。