1.什么是协程?
//本质上,协程是轻量级的线程。
//线程的框架(API)
//为什么不直接用Java的线程?回调地狱,编码风格
GlobalScope.launch{} //后台运行的新的协程
delay 是一个特殊的 挂起函数 ,它不会造成线程阻塞,但是会 挂起 协程,并且只能在协程中使用。
//作为用户感觉上,阻塞和挂起是一样的 //挂起:一般是主动的,由系统或程序发出,甚至于辅存中去。(不释放CPU,可能释放内存,放在外存)
//阻塞:一般是被动的,在抢占资源中得不到资源,被动的挂起在内存,等待某种资源或信号量(即有了资源)将他唤醒。(释放CPU,不释放内存)
//挂起,是车子把货卸了,但是车还在开,车有可能又在拖其他货物了
//阻塞,是货没卸,车子停了,在等红绿灯,变绿灯了就走
fun main(){
//后台运行的新的协程
GlobalScope.launch {
//delay 是一个特殊的 挂起函数 ,它不会造成线程阻塞,
// 但是会 挂起 协程,并且只能在协程中使用。
delay(1000)
println("World!")
}
println("Hello,")
//协程已在等待时主线程还在继续,阻塞主线程2秒钟来保证JVM存活
Thread.sleep(2000)
}
2.runBlocking {}等待一个作业
//延迟一段时间来等待另一个协程运行并不是一个好的选择。
//让我们显式(以非阻塞方式)等待所启动的后台 Job 执行结束:
//POSIX线程
fun main() = runBlocking<Unit> { //开始执行主协程
val job = GlobalScope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // 等待直到子协程执行结束
//....
}
fun main(){
GlobalScope.launch {
delay(1000)
println("World!")
}
println("Hello,")
//这个表达式阻塞了主线程,我们延迟 2 秒来保证 JVM 的存活
runBlocking {
delay(2000L)
}
//结果是相似的,但是这些代码只使用了非阻塞的函数 delay。
//调用了 runBlocking 的主线程会一直阻塞直到 runBlocking 内部的协程执行完毕。
}
3.job.join() // 等待直到子协程执行结束
//这个示例可以使用更合乎惯用法的方式重写,使用 runBlocking 来包装 main 函数的执行:
//把主线程包装了一个Kotlin的携程
fun main() = runBlocking<Unit> { //开始执行主协程
GlobalScope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
delay(2000L)
}
4.简化写法
//结构化并发
//我们可以在执行操作所在的指定作用域内启动协程,
//而不是像通常使用线程(线程总是全局的)那样在 GlobalScope 中启动。
//Cooperation 合作,协作
//Routine 日常,常规的事物
//在我们的示例中,我们使用 runBlocking 协程构建器将 main 函数转换为协程。
//包括 runBlocking 在内的每个协程构建器都将 CoroutineScope 的实例添加到其代码块所在的作用域中。
//我们可以在这个作用域中启动协程而无需显式 join 之,因为外部协程(示例中的 runBlocking)
//直到在其作用域中启动的所有协程都执行完毕后才会结束。
fun main() = runBlocking<Unit> {
launch {
delay(1000L)
println("World!")
}
println("Hello,")
}
5.作用域构建器
//除了由不同的构建器提供协程作用域之外,还可以使用 coroutineScope 构建器声明自己的作用域。
//它会创建一个协程作用域并且在所有已启动子协程执行完毕之前不会结束。
//runBlocking 与 coroutineScope 可能看起来很类似,因为它们都会等待其协程体以及所有子协程结束。
//主要区别在于,runBlocking 方法会阻塞当前线程来等待,而 coroutineScope 只是挂起,会释放底层线程用于其他用途。
//由于存在这点差异,runBlocking 是常规函数,而coroutineScope是挂起函数。
fun main() = runBlocking<Unit> {
launch {
delay(200L)
println("2 Task from runBlocking")
}
// 创建一个协程作用域
coroutineScope {
launch {
delay(500L)
println("3 Task from nested launch")
}
delay(100L)
println("1 Task from coroutine scope")
}
println("4 Coroutine scope is over")
}
6.提取函数重构
//在协程内部可以像普通函数⼀样使⽤挂 起函数,
//不过其额外特性是,同样可以使⽤其他挂起函数(如本例中的 delay )来挂起协程的执⾏。
//需要使用 suspend 修饰
fun main() = runBlocking<Unit> {
launch { doWorld() }
println("Hello,")
}
suspend fun doWorld(){
delay(1000L)
println("World!")
}
7.repeat(int x){ }多次执行协程
// x:代表启动协程数量,协程很轻量
//它启动了1000个协程,并且在 5 秒钟后,每个协程都输出一个点
//启动了10万个协程,不代表启动了10万个线程,以下为并行执行
fun main() = runBlocking<Unit> {
repeat(100000){
launch {
delay(1000)
print(".")
}
}
}
GlobalScope.launch {}会随着runBlocking结束一起结束不会执行1000次,以下方式为串行执行
fun main() = runBlocking<Unit> {
GlobalScope.launch {
repeat(1000){ i ->
delay(500L)
println("I'm sleeping $i")
}
}
delay(3300L)
}
8.job.cancelAndJoin()//取消协程的执⾏
job.cancelAndJoin() 等同于 job.cancel() + job.join()
//我们之前 没有在控制台上看到堆栈跟踪信息的打印。
//这是因为在被取消的协程中 CancellationException 被认为 是协程执⾏结束的正常原因。
fun main() = runBlocking<Unit> {
val job = launch {
repeat(1000){ i ->
delay(500L)
println("job:I'm sleeping $i")
}
}
delay(1300L)
println("main: I'm tired of waiting!")
// job.cancel()
// job.join()
job.cancelAndJoin()
println("main:Now I can quit!")
}
//协程的取消是协作的
//如果协程正在执⾏计算任务,那么它是不能被取消的
如下代码会一直执行直到循环结束
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val job = launch {
var nextPrintTime = startTime
var i = 0
//这里没有挂起函数
while(i < 10){ // ⼀个执⾏计算的循环,只是为了占⽤ CPU
if(System.currentTimeMillis() >= nextPrintTime){
println("job:I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L)
println("main:I'm tired of waiting!")
job.cancelAndJoin()
println("main:Now I can quit.")
}
//使计算代码可取消
//显式的检查取消状态
//isActive 是⼀个可以被使⽤在 CoroutineScope 中的扩展属性
launch(Dispatchers.Default) {}
代码会被job.cancelAndJoin()//取消协程的执⾏
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
//isActive 是⼀个可以被使⽤在 CoroutineScope 中的扩展属性
while(isActive){
if(System.currentTimeMillis() >= nextPrintTime){
println("job:I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L)
println("main:I'm tired of waiting!")
job.cancelAndJoin()
println("main:Now I can quit.")
}
9.在 finally 中释放资源
fun main() = runBlocking {
val job = launch {
try {
repeat(1000) { i ->
println("job:I'm sleeping $i ...")
delay(500L)
//假如在这里释放资源
}
} finally {
//在这里释放资源
// 任何尝试在 finally 块中调⽤挂起函数的⾏为都会抛出 CancellationException
// 因为这⾥ 持续运⾏的代码是可以被取消的
// delay(1000L)
println("job:I'm running finally")
}
}
delay(1300L)
println("main:I'm tired of waiting!")
job.cancelAndJoin()
println("main:Now I can quit.")
}
//在finally中使用挂起函数
在前⼀个例⼦中任何尝试在 finally 块中调⽤挂起函数的⾏为都会抛出 CancellationException,因为这⾥ 持续运⾏的代码是可以被取消的。通常,这并不是⼀个问题,所有良好的关闭操作(关闭⼀个⽂件、取消⼀个作 业、或是关闭任何⼀种通信通道)通常都是⾮阻塞的,并且不会调⽤任何挂起函数。然⽽,在真实的案例中,当你 需要挂起⼀个被取消的协程,你可以将相应的代码包装在 withContext(NonCancellable) {……} 中,并 使⽤ withContext 函数以及 NonCancellable 上下⽂,⻅如下⽰例所⽰
fun main() = runBlocking {
val job = launch {
try {
repeat(1000) { i ->
println("job:I'm sleeping $i ...")
delay(500L)
//假如在这里释放资源
}
} finally {
withContext(NonCancellable){
println("job:I'm running finally")
delay(1000L)
println("job: And I've just delayed for 1 sec because I'm non-cancellable")
}
}
}
delay(1300L)
println("main:I'm tired of waiting!")
job.cancelAndJoin()
println("main:Now I can quit.")
}
9.超时
//在实践中绝⼤多数取消⼀个协程的理由是它有可能超时。
//withTimeout 抛出了 TimeoutCancellationException ,它是 CancellationException 的⼦类。
//我们之前 没有在控制台上看到堆栈跟踪信息的打印。
//这是因为在被取消的协程中 CancellationException 被认为 是协程执⾏结束的正常原因。
fun main() = runBlocking {
withTimeout(1330L){
repeat(1000){ i ->
println("I'm sleeping $i")
delay(500L)
}
}
}
上述例子会抛出异常
withTimeoutOrNull 通过返回 null 来进⾏超时操作,从⽽替代抛出⼀个异常:
fun main() = runBlocking {
val result = withTimeoutOrNull(1330L){
repeat(100){ i ->
println("I'm sleeping $i")
delay(500L)
}
"OK"
}
println(result ?: "Done")
}
下边例子同在finally中释放资源
var acquired = 0
//伪代码
class Resource {
init {
acquired++
}
fun close() {
acquired--
}
}
//不总是打印输出0,取决于计算机性能,一旦超时,释放资源的操作将不会执行
/*fun main() {
runBlocking {
repeat(100000){
launch {
val resource = withTimeout(60){
delay(59)
Resource()
}
resource.close()
}
}
}
//非0,有内存泄漏
//0代表资源都释放了,没有内存泄漏
println(acquired) //期待值是0
}*/
//释放资源的操作,放入finally当中
fun main() {
runBlocking {
repeat(100000) {
launch {
var resource: Resource? = null
try {
withTimeout(60) {
delay(59)
resource = Resource()
}
} finally {
resource?.close()
}
}
}
}
//0代表资源都释放了,没有内存泄漏
println(acquired) //期待值是0
}
10.async相关问题
//默认顺序调⽤
fun main() = runBlocking {
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")
}
suspend fun doSomethingUsefulOne(): Int {
println("doSomethingUsefulOne")
//所有kotlinx.coroutines中的挂起函数都是可被取消的。
delay(1000L)
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
println("doSomethingUsefulTwo")
delay(1000L)
return 29
}
//使⽤ async 并发
fun main() = runBlocking {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
//这⾥快了两倍,因为两个协程并发执⾏。请注意,使⽤协程进⾏并发总是显式的。
println("Completed in $time ms")
}
suspend fun doSomethingUsefulOne(): Int {
println("doSomethingUsefulOne")
//所有kotlinx.coroutines中的挂起函数都是可被取消的。
delay(1000L)
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
println("doSomethingUsefulTwo")
delay(1000L)
return 29
}
//惰性启动的 async
//在计算⼀个值涉及挂起函数时,这个 async(start = CoroutineStart.LAZY)
//的⽤例⽤于替代标准库中的 lazy 函数。
fun main() = runBlocking {
val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
//执行如下代码是并发运行。
//delay(3000L)
//one.start()
//two.start()
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
suspend fun doSomethingUsefulOne(): Int {
println("doSomethingUsefulOne")
//所有kotlinx.coroutines中的挂起函数都是可被取消的。
delay(1000L)
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
println("doSomethingUsefulTwo")
delay(1000L)
return 29
}
//async⻛格的函数
fun main(){
val time = measureTimeMillis {
val one = doSomethingUsefulOneAsync()
val two = doSomethingUsefulTwoAsync()
runBlocking {
println("The answer is ${one.await() + two.await()}")
}
}
println("Completed in $time ms")
}
fun doSomethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}
fun doSomethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
suspend fun doSomethingUsefulOne(): Int {
println("doSomethingUsefulOne")
//所有kotlinx.coroutines中的挂起函数都是可被取消的。
delay(1000L)
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
println("doSomethingUsefulTwo")
delay(1000L)
return 29
}
//如果其中⼀个⼦协程(即 two )失败,第⼀个 async 以及等待中的⽗协程都会被取消
fun main() = runBlocking<Unit>{
try {
failedConcurrentSum()
}catch (e:ArithmeticException){
println("Computation failed with ArithmeticException")
}
}
suspend fun failedConcurrentSum(): Int = coroutineScope {
val one = async<Int> {
try{
delay(Long.MAX_VALUE)
42
}finally {
println("First child was cancelled")
}
}
val two = async<Int> {
println("Second child throw an Exception.")
throw ArithmeticException()
}
one.await() + two.await()
}
11.上下文
//协程总是运⾏在⼀些以 CoroutineContext 类型为代表的上下⽂中
//协程上下⽂是各种不同元素的集合。其中主元素是协程中的 Job
//所有的协程构建器诸如 launch 和 async 接收⼀个可选的 CoroutineContext 参数,
//它可以被⽤来显式的为⼀ 个新协程或其它上下⽂元素指定⼀个调度器。
//当调⽤ launch { …… } 时不传参数,它从启动了它的 CoroutineScope 中承袭了上下⽂(以及调度器)。
//在这 个案例中,它从 main 线程中的 runBlocking 主协程承袭了上下⽂。
launch {
delay(1000)
println("I'm working in thread: ${Thread.currentThread().name}")
}
// 不受限的——将⼯作在主线程中
// ⾮受限的调度器⾮常适⽤于执⾏不消耗 CPU 时间的任务,以及不更新局限于特定线程的任何共享数据(如UI)的协程。
launch(Dispatchers.Unconfined) {
println("Unconfined : I'm working in thread: ${Thread.currentThread().name}")
}
// 将会获取默认调度器
// 默认调度器使⽤共享 的后台线程池。
launch(Dispatchers.Default) {
println("Default : I'm working in thread: ${Thread.currentThread().name}")
}
// 将使它获得⼀个新的线程
// ⼀个专⽤的线程是⼀种⾮常昂贵的资源。
launch(newSingleThreadContext("MyOwnThread")) {
println("newSingleThreadContext: I'm working in thread: ${Thread.currentThread().name}")
}
//⾮受限调度器 vs 受限调度器
fun main() = runBlocking<Unit> {
//协程可以在⼀个线程上挂起并在其它线程上恢复。
//不适合更新UI
launch(Dispatchers.Unconfined){
println("Unconfined : I'm working in thread: ${Thread.currentThread().name}")
delay(500L)
// 挂起之后,这里是子线程
println("Unconfined : After delay in thread: ${Thread.currentThread().name}")
}
launch {
println("main runBlocking: I'm working in thread: ${Thread.currentThread().name}")
delay(1000)
println("main runBlocking: After delay in thread: ${Thread.currentThread().name}")
}
}
12.⼦协程作用域
//当⼀个⽗协程被取消的时候,所有它的⼦ 协程也会被递归的取消。
// 孵化了两个⼦作业, 其中⼀个通过 GlobalScope 启动
// 当使⽤ GlobalScope 来启动⼀个协程时,则新协程的作业没有⽗作业。 //因此它与这个启动的作⽤域⽆关 且独⽴运作。
fun main() = runBlocking<Unit> {
//当⼀个⽗协程被取消的时候,所有它的⼦ 协程也会被递归的取消。
val request = launch {
// 孵化了两个⼦作业, 其中⼀个通过 GlobalScope 启动
// 当使⽤ GlobalScope 来启动⼀个协程时,则新协程的作业没有⽗作业。
//因此它与这个启动的作⽤域⽆关 且独⽴运作。
GlobalScope.launch {
println("job1: I run in GlobalScope and execute independently!")
delay(1000)
println("job1: I am not affected by cancellation of the request")
}
launch {
delay(100)
println("job2: I am a child of the request coroutine")
delay(1000)
println("job2: I will not execute this line if my parent request is cancelled")
}
}
delay(500)
request.cancel()
delay(1000)
println("main: Who has survived request cancellation?")
}
13.⽗协程的职责
//⼀个⽗协程总是等待所有的⼦协程执⾏结束。
//⽗协程的职责
//⼀个⽗协程总是等待所有的⼦协程执⾏结束。
fun main() = runBlocking<Unit> {
val request = launch {
repeat(3){ i ->
launch {
delay((i + 1) * 200L)
println("Coroutine $i is done")
}
}
println("request: I'm done and I don't explicitly join my children that are still active")
}
//request.join()
println("Now processing of the request is complete")
}