Kotlin协程 - - -启动与取消、作用域

一.协程的启动

1.协程构建器

launch与async构建器都用来启动新协程:

launch,返回一个Job并且不附带任何结果值。

async,返回一个Deferred,Deferred也是一个job,可以使用await在一个延期的值上得到它的最终结果。

fun main() {
    testCoroutine();
}
// runBlocking 主协程   他会等子协程任务执行完再关闭,在此之前都是堵塞状态
fun testCoroutine()= runBlocking {
    //async的await函数会延时返回一个结果值,launch不能
    val job = launch {
        delay(2000)
        println( "launch finished")
    }
    val job1 = async {
        delay(2000)
        println( "async finished")
         "async result"
    }
    println(job1.await())
}

打印结果:

launch finished
async finished 
async result

2.launch与async的顺序执行

我们经常会碰到这样一个情况,在调取一个接口获取信息后再调取其他接口,这样按顺序调取。

1.launch

fun main() {
    testCoroutine1();
}
//runBlocking 主协程   他会等子协程任务执行完再关闭,在此之前都是堵塞状态
fun testCoroutine1()= runBlocking {
    //都是异步任务
    val launch = launch {
        delay(100)
      println("One")
    }
    //join是一个挂起函数  他会暂停协程直到所有的任务完成
    launch.join()

    val launch1 = launch {
        delay(100)
        println("Two")

    }
    val launch2 = launch {
        delay(100)
        println("Three")
    }
}

打印结果:

One
Two
Three

2.async

fun main() {
    testCoroutine1();
}
//runBlocking 主协程   他会等子协程任务执行完再关闭,在此之前都是堵塞状态
fun testCoroutine1()= runBlocking {

    val async = async {
        delay(100)
      println("One")
    }
    async.await()

    val async1 = async {
        delay(100)
        println("Two")

    }
    val async2 = async {
        delay(100)
        println("Three")
    }
}

打印结果:

One
Two
Three

3.async结构化并发

fun main() {
    TestSync()
}
//runBlocking 主协程   他会等子协程任务执行完再关闭,在此之前都是堵塞状态
fun  TestSync()= runBlocking {
    val time = measureTimeMillis {
        //这样可以同时开始两个子协程执行并发任务
        val doOne =    async {doOne()  }
        val doTwo =    async {doTwo()  }

                            //这里是调用的同步方法等全部执行完后进行运算
        print("The result:${doOne.await() + doTwo.await()} \n")
    }
    print("Completed in $time ms")
}

suspend fun  doOne():Int {
    delay(1000)
    return 20
}

suspend fun  doTwo():Int {
    delay(1000)
    return 25
}

打印结果

The result:45
Completed in 1027 ms

这里可以看出我在两个获取值的函数里各延时1秒,但由于结构化并发的原因整个方法执行时间就只用了一秒。

4.协程的启动模式

DEFAULT:协程创建后,立即开始调度,在调度前如果协程被取消,其将直接进入取消
响应的状态。

 fun testStartMode() = runBlocking {
        
        val job = launch(start = CoroutineStart.DEFAULT) {
            var i = 0
            while (true){
                i++
            }
            println("finished.")
        }
        delay(1000)
        job.cancel()
    }

ATOMIC:协程创建后,立即开始调度,协程执行到第一个挂起点之前不响应取消。

 fun testStartMode() = runBlocking {
        
        val job = launch(start = CoroutineStart.ATOMIC) {
           //....
            //在执行到第一个挂起点之前取消无效
            delay(1000)
            println("finished.")
        }
      
    }

LAZY:只有协程被需要时,包括主动调用协程的start、join或者await等函数时才会开始
调度,如果调度前就被取消,那么该协程将直接进入异常结束状态。

 fun testStartMode() = runBlocking {
        
     //惰性启动
        val job = async (start = CoroutineStart.LAZY) {
            29
        }
        //执行一些计算
        //再启动
        job.start()
      
    }

UNDISPATCHED:协程创建后立即在当前函数调用栈中执行,直到遇到第一个真正挂起

 fun testStartMode() = runBlocking {
        
 //在哪个线程中执行?
        val job = launch(context = Dispatchers.IO, start = CoroutineStart.UNDISPATCHED) {
            println("thread:"+Thread.currentThread().name)
        }
      
    }

UNDISPATCHED 启动模式会立即在当前线程执行,既可能会在主线程执行。

5.协程作用域


//作用域构建器
    //runBlocking是常规函数,而coroutineScope是挂起函数。
    //它们看起来很类似,它们都会等待其协程体以及所有子协程结束。
    //主要区别在于runBlocking方法会阻塞当前线程来等待,而coroutineScope只是挂起,会释放底层线程用于其他用途。
fun  testCoroutineScope () = runBlocking {
   
 //作用域的构建器  这里继承了父作用域  上下文也会继承
    //coroutineScope
  //一个协程失败了,所有其他兄弟协程也会被取消
     coroutineScope {
            val job1 = launch {
                delay(400)
                println("job1 finished.")
            }
            val job2 = launch {
                delay(200)
                println("job2 finished.")
                throw IllegalArgumentException()
            }
        }
     //supervisorScope
    //一个协程失败了,不会影响其他兄弟协程
        supervisorScope {
            val job1 = launch {
                delay(400)
                println("job1 finished.")
            }
            val job2 = launch {
                delay(200)
                println("job2 finished.")
                throw IllegalArgumentException()
            }
        }
    //这里因为继承了runBlocking的作用域coroutineScope和supervisorScope执行完后runBlocking才会完成
   
}

6.Job对象的生命周期

对于每一个创建的协程(通过launch或者async),会返回一个Job实例,该实例是协程的唯一标示,并且负责管理协程的生命周期。
一个任务可以包含一系列状态:
新创建New)、活跃Active)、完成中Completing)、已完成Completed)、取消Cancelling)和已取消Cancelled)。
虽然我们无法直接访问这些状态,但是我们可以访问Job的属性:isActive、isCancelled和isCompleted。

如果协程处于活跃状态,协程运行出错或者调用 job.cancel() 都会将当前任务置为取消中 (Cancelling) 状态 (isActive = false, isCancelled = true)。
 当所有的子协程都完成后,协程会进入已取消 (Cancelled) 状态,此时 isCompleted = true

二.取消协程

1.协程的取消

1.取消作用域会取消它的子线程

    fun testScopeCancel()= runBlocking {
        /*这里又建了一个协程作用域,有新的上下文,而下面的两个子协程并不算runBlocking
        的子协程,所以在下面如果不延时就直接程序执行完关闭了*/
        val scope= CoroutineScope(Dispatchers.Default)  
       scope.launch {
            delay(1000)
            print("job 1")
        }

        scope.launch {
            delay(1000)
            print("job 2")
        }
        delay(100)
        //scope作用域对象 他一取消作用域内的子协程都会取消
        scope.cancel()
        delay(2000)

    }

CoroutineScope(Dispatchers.Default)coroutineScope的区别在于前者有自己的作用域和上下文,而coroutineScope会继承父协程的作用域与上下文

 
  

2.被取消的子协程并不会影响其余兄弟协程

fun testScopeCancel()= runBlocking {
        /*这里又建了一个协程作用域,有新的上下文,而下面的两个子协程并不算runBlocking的子协程,
         所以在下面如果不延时就直接程序执行完关闭了*/
        val scope= CoroutineScope(Dispatchers.Default)      
        val launch = scope.launch {
            delay(1000)
            print("job 1")
        }

        scope.launch {
            delay(1000)
            print("job 2")
        }
        delay(100)
        //子协程取消不会影响到其他的子协程
        launch.cancel()
        delay(2000)
    }

3.抛出异常来中断协程

fun main() {
    testCancellationException()
}
fun testCancellationException()= runBlocking {

    val launch = GlobalScope.launch {
        try {
         //这里因为协程被取消了,会抛出一个异常
            delay(1000)
            print("job 1")
        }catch (e :Exception){
            e.printStackTrace()
        }
    }
    
    launch.cancel()
    //launch .cancel(CancellationException("取消"))这里也可以抛出异常
    //能让runBlocking等待GlobalScope.launch执行完毕
    launch.join()
}

异常打印:

kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@306b9c53

2.CPU密集型任务取消

//CPU密集型运算的协程不能取消,可以借用协程生命周期让运算停止后取消
fun testCancelCpuTaskByIsActive()= runBlocking{
    val startTime = System.currentTimeMillis()
    val launch = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0;
        //yield也会抛一个异常,还有会出让一部分的执行权(就算出让线程的资源,但这不意味着他不会再执行了,他还会在执行)
        //如果要处理的任务属于:
        //1) CPU 密集型,2) 可能会耗尽线程池资源,3) 需要在不向线程池中添加更多线程的前提下允许线 程处理其他任务,那么请使用 yield()。                         
        //yield()
        //ensureActive()此方法为抛异常结束协程 如果job处于非活跃状态,这个方法会立即抛出异常。
        //ensureActive()
        // isActive是一个可以被使用在CoroutineScope中的扩展属性,检查Job是否处于活跃状态。
        //当协程调用cancel方法时 isActive为false 运算停止
        while (i < 5 && isActive) {
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("job:I'm sleeping ${i++}")
                nextPrintTime += 500
            }
        }
    }

    delay(1500)
    println("main: I'm tired of waiting!")
    launch.cancelAndJoin()
    println("main: Now I can quit.")
}

3.协程取消后释放资源

1.通过finally释放资源

 //在 finally 中释放资源
   
    fun testReleaseResources() = runBlocking {
        val job = launch {
            try {
                repeat(1000) { i ->
                    println("job: I'm sleeping $i ...")
                    delay(500L)
                }
            } finally {
                println("job: I'm running finally")
            }
        }
        delay(1300L) // 延迟⼀段时间
        println("main: I'm tired of waiting!")
        job.cancelAndJoin() // 取消该作业并且等待它结束
        println("main: Now I can quit.")
    }

2。use函数

//use函数:该函数只能被实现了Closeable的对象使用,程序结束的时候会自动调用close方法,适合文件对象
 fun testReleaseResources() = runBlocking {
BufferedReader(FileReader("D:\\I have a dream.txt")).use {
            var line: String?

            while (true) {
                line = it.readLine() ?: break //读取一行数据,若为空则退出循环
                println(line) //打印读取的数据
            }
        }
}

4.在finally中执行协程中断后的任务

    fun testReleaseResources() = runBlocking {
        val launch = launch {
            try {
                repeat(1000) { i ->
                    println("job: I'm sleeping $i ...")
                    delay(500L)
                }
                //一般协程取消后finally中的任务不会继续执行
            } finally {
                //协程执行后可以在finally释放资源
                println("job: I'm running finally")
                //要这样才能再取消协程后继续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")
                }
            }
        }
    }

5.处理超时任务

fun main() {
    testDealWithTimeout()
}

fun testDealWithTimeout()= runBlocking{
    //处理耗时任务用这个方法     
    //超过1300毫秒算这个任务超时
    val withTimeout = withTimeoutOrNull(1300) {
        repeat(1000) { i ->
            println("job: I'm sleeping $i ...")
            delay(500L)
        }
        "Done"
    }?:"Jack"
     //执行成功返回 Done  超时返回 Jack
    println("Result is $withTimeout ...")
}

你可能感兴趣的:(Kotlin协程,kotlin,android,java)