轻量级线程——Kotlin之协程

轻量级线程——Kotlin之协程

协程是一种“轻量级线程“,从Kotlin 1.1开始引入。由于一些耗时操作(如网络IO、文件 IO、CPU或GPU密集型任务)会使线程阻塞直到操作完成,协程提供了一种避免线程阻塞、开销更小且更加可控的异步操作。

协程的基本原理

协程完全通过编译技术实现(不需要来自 VM 或 OS 端的支持),挂起通过代码来生效。基本上,每个挂起函数都转换为状态机,其中的状态对应于挂起调用。刚好在挂起前,下一状态与相关局部变量等一起存储在编译器生成的类的字段中。在恢复该协程时,恢复局部变量并且状态机从刚好挂起之后的状态进行。

为什么需要协程

阻塞还是挂起

协程可以被挂起而不阻塞线程,线程的阻塞代价是昂贵的。尤其是创建线程数量是有限的,当线程被阻塞,那些重要的任务就有可能被延迟。协程的挂起几乎是没有代价的,它不需要切换上下文。协程的本质是最大限度的利用线程所获得的cpu时间,提高线程的效率。

协程的简单使用——launch函数

使用launch函数

    public fun launch(
    context: CoroutineContext = DefaultDispatcher,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit): Job {}

第一个参数为协程的上下文

第二个参数为协程的启动选项

第三个参数为一个suspend修饰的函数

    fun test(){
        launch(Unconfined) {
            delay(1000)
            print("I am a Coroutine")
        }
        print("I am mainThread")
    }

这里写图片描述

launch()函数开启了一个协程,由于协程被挂起,所以协程之外的代码先被执行,又因为协程所处的上下文已经销毁,所以不能完整执行结束。

注意:suspend函数只能用于启动协程的函数(闭包)中

launch函数返回一个Job借口对象,可以对协程进行管理。

Job接口的常用方法有

  • isActive
  • isCompleted
  • isCanceled
  • start
  • cancel
  • join

Job的几种状态

轻量级线程——Kotlin之协程_第1张图片

协程的生命周期

轻量级线程——Kotlin之协程_第2张图片

延迟一个协程

fun test(){
    val task =  launch(Unconfined,CoroutineStart.LAZY) {
        println("I am a Coroutine")
    }
    println("I am mainThread")
    task.start()
}

这里写图片描述

finally中的协程代码

    fun test(){
    val task = launch(Unconfined) {
        try{
            delay(1000)
            println("try at coroutine")
        }finally {
            println("finally at coroutine")
        }
    }
    task.cancel()
    Thread.sleep(2000)
    println("I am mainThread")
    }

这里写图片描述

协程取消时,finally语句依然会被执行。但是当协程所在线程已经停止,finally也会停止,不会继续执行。可以使用NonCancellable让finallly完整执行。

    fun test(){
    val task = launch(Unconfined) {
        try{
            println("try at coroutine")
        }finally {
            run(NonCancellable) {
                delay(2000)
                println("finally at coroutine")
            }
        }
    }
    println("I am mainThread")
    task.cancel()
    }

协程的超时时间


fun test(){
    launch(Unconfined) {
        withTimeout(1000){
            try{
                println("try at coroutine")
            }finally {
                delay(2000)
                println("finally at coroutine")
            }
        }
    }
    println("I am mainThread")
    Thread.sleep(2000)
}

这里写图片描述

取消一个计算协程

    fun test(){
    val task = launch(Unconfined) {
        var i=0
        while(true){
            i++
            println(i)
            delay(200)
        }
    }
    Thread.sleep(1000)
    if(task.isActive) task.cancel()
    Thread.sleep(1000)
    println("I am mainThread")
    }

轻量级线程——Kotlin之协程_第3张图片

协程的简单使用——async函数

使用async函数开启异步协程

将launch函数换成async即可启动异步协程

    fun test(){
    async {
        repeat(10){
            delay(200)
            println("task1")
        }
    }

    async {
        repeat(10){
            delay(200)
            println("task2")
        }
    }

    Thread.sleep(2000)
    println("I am mainThread")
    }

轻量级线程——Kotlin之协程_第4张图片

协程的简单使用——runBlocking函数

runBlocking用于main函数或test函数,用于实现类似主协程的效果

    fun main(args: Array) = runBlocking {
    launch {
        println("before delay")
        delay(1000)
        println("after delay")
    }
    delay(2000)
    //Thread.sleep(2000)
    println("I am mainThread")
    }

这里写图片描述

Android中使用协程

协程的优点主要是①数量多②非阻塞③管理方便④异步并发时同步简单。

所以可以使用launch(async)+async+async来启动两个异步任务并直接在launch更新UI,无需使用handler机制;多线程之间同步也不需要写过多的代码。

异步协程launch+async+async

    fun testAsync(){
        //开启两个异步任务;这里只能用async,因为只有async有await()获取结果,并且异步
        val task1 = async {
            repeat(100){
                Log.d("Task1","当前线程:${Thread.currentThread().name}")
            }
            "AsyncTask1"
        }
        val task2 = async {
            repeat(100){
                Log.d("Task2","当前线程:${Thread.currentThread().name}")
            }
            "AsyncTask2"
        }
        //更新UI或async
        launch(Unconfined) {
            Log.d("UI1","当前线程:${Thread.currentThread().name}")
            //当前UI线程的协程阻塞,但是不会使UI阻塞
            text1.text = task1.await()
            text2.text = task2.await()
            Log.d("UI2","当前线程:${Thread.currentThread().name}")
        }
    }

同步协程launch+async+async

    fun testSync(){
        launch(Unconfined) {
            Log.d("UI1","当前线程:${Thread.currentThread().name}")
            val res1 = async {
                repeat(100){
                    Log.d("Task1","当前线程:${Thread.currentThread().name}")
                }
                "AsyncTask1"
            }.await()  //挂起
            val res2 = async {
                repeat(100){
                    Log.d("Task2","当前线程:${Thread.currentThread().name}")
                }
                "AsyncTask2"
            }.await()
            Log.d("UI2","当前线程:${Thread.currentThread().name}")
            text1.text = res1
            text2.text = res2
        }
    }

以上,我们可以使用launch或者async+几个async来代替以前的多线程。而当我们只需要让几个任务串行而不需要返回值,可以只用launch;当我们需要几个异步或者需要获得返回值,就用async,没有特殊需求就可自由选用。

总结

  • 线程创建的数量是有限的,创建和销毁线程的开销很大,协程几乎不需要有额外的开销
  • 协程挂起不会引起线程阻塞,阻塞可能会导致重要任务的延迟
  • 协程更加容易管理,如果要管理线程,则需要自己实现future借口或使用线程池。
  • 异步协程的await()可以直接获取获取返回值而不需要去写额外的同步代码。

你可能感兴趣的:(android,Kotlin)