### 什么是协程
子程序或者称为函数,在所有的语言中都是层级调用,如:A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。所以子程序是 通过栈来实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。
而协程看上去是子程序,执行的过程中,在 子程序中可中断,去执行其他的子程序,在适当的时候可以回来接着执行。
官方的定义:协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。
协程可以简化异步编程,可以顺序地表达程序,协程也提供了一种避免阻塞线程并用更廉价、更可控的操作替代线程阻塞的方法 – 协程挂起。
### 协程使用场景
协程是一种轻量级的方便操作异步代码的语法糖,而它本身不提供异步能力。
Java 中程序 出现异常,通常会在回调中加入一个 onError 传递异常信息。
interface Callback{
fun onError(e: CertainException)
fun onSuccess(data: Data)
}
在协程的只支持下,我们只要按照同步代码的异常捕获方式进行捕获就可以
async{
try{
val bitmap = await{ loadImage(url) }
}catch(e: CertainException){
...
}
}
协程更加的轻量级,因为协程只是 一块内存,而线程是对应 操作系统的内核线程。
### 线程和协程区别
1.协程是编译器级别的,线程是系统级别的,协程的切换是由程序来控制的,线程的切换是由操作系统来控制的。
2.协程是协作式的,线程式抢占式的,协程式由程序来控制怎么执行切换;而线程是由系统来决定线程之间的切换。
3.一个线程可以包含多个协程。
4.Java中,多线程可以充分利用多核cpu,协程是在一个线程中执行。
5.协程适合io密集型的程序,多线程适合计算密集型的程序(适用于多核cpu情况),当程序中大部分是 文件读写和网络请求操作,这时首选 协程。协程的执行效率较高,子程序的切换时由程序自身控制,因此没有线程切换的开销。
6.使用协程可以顺序调用异步代码,避免回调地狱。
### 协程用法
//这段代码就是启动一个协程,并启动,延迟1秒后打印world,就从这个launch方法进行切入
GlobalScope.launch {
delay(1000L) // 延迟(挂起)1000毫秒,注意这不会阻塞线程
println("World!")
}
怎么选择启动模式,协程默认是 Deafault,就是创建完成立即启动,当然我们也可以设置位 LAZY,来安排什么时候启动。
val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
delay(1000L)
LogUtils.i("AndroidActivity"+"设置启动模式")
}
LogUtils.i("AndroidActivity"+"hello world")
job.start()
### 协程之间的切换
//这段代码先打印出next,然后延迟1秒钟后打印出now,像是android里handler的post和postDelay方法。
GlobalScope.launch(Dispatchers.Default){
LogUtils.i("AndroidActivity"+"协程间是如何切换的")
LogUtils.i("AndroidActivity---${Thread.currentThread().name}")
launch {
delay(1000)
LogUtils.i("AndroidActivity"+"now")
}
LogUtils.i("AndroidActivity"+"next")
}
### 协程如绑定在指定的线程中
首先创建一个 CoroutineScope (协同程序范围),所有协程都会运行在 CoroutineScope,在CoroutineScope中传入参数 Dispatchers.Main,这是一个协程调度器,规定了协程在执行的时候,使用一个或多个线程,协程调度器可以将协程执行局限在指定的线程中,调度它运行在线程池中或让它不受限的运行。
调用 launch启动一个协程,launch方法会返回一个job,调用 cancel 方法可以取消协程的运行。
调用 async 又启动一个协程,同时调用 Dispatchers.Default这个协程调度器,使协程在执行时使用一个DefaultDispatcher-worker-1线程,
为什么使用async 而没有使用launch?
因为async会返回一个Deferred对象,调用其await方法可以阻塞执行流等到协程执行完毕返回结果,这样可以得到一个返回值,在这个async创建的协程里使用了使用了suspend方法先休眠2秒钟,然后返回一个字符串,注意这里这个delay也是suspend方法,一个suspend方法只能在协程或者suspend方法里调用。
private fun coroutineScope() {
val uiScope = CoroutineScope(Dispatchers.Main)
uiScope.launch {
LogUtils.i("AndroidActivity"+"模拟发送请求")
val deffer = async(Dispatchers.Default) {
getResult()
}
val result = deffer.await()
LogUtils.i("AndroidActivity---获取 $result")
}
}
private suspend fun getResult(): String {
delay(2000L)
LogUtils.i("AndroidActivity"+"模拟请求时间")
return "result"
}
//打印日志如下
AndroidActivity模拟发送请求
2秒后
AndroidActivity模拟请求时间
AndroidActivity---获取 result
### 协程Job类
调用launch,就启动了一个协程,launch方法会返回一个job, job.cancel()取消一个协程
fun main() {
val job = GlobalScope.launch {
delay(1000L)
println("World!")
}
job.cancel()
println("Hello,")
}
join()等待协程执行完毕:作用很像Thread.join()函数,join()后面的代码会等到协程结束再执行,
fun main() = runBlocking {
val job = GlobalScope.launch {
delay(1000L)
println("World!")
delay(1000L)
}
println("Hello,")
job.join()
println("Good!")
}
//依次打印
Hello,
World!
Good!
job.cancelAndJoin()取消并且加入一个协程,等待协程执行完毕然后再取消 这是一个 Job 的扩展函数,它结合了 cancel 和 join的调用
public suspend fun Job.cancelAndJoin() {
cancel()
return join()
}