当前位置: 首页>移动开发>正文

Android kotlin 函数 主线程等待子协程 返回的结果 return kotlin 协程 切换线程


Kotlin协程

  • 前言
  • 协程基本概念和使用
  • 协程使用的基本函数
  • 示例代码


抓住今天,尽可能少的信赖明天。 喝汤能补 (* ^ ▽ ^ *)

前言

  该文章作为学习交流,如有错误欢迎各位大佬指正 (* ^ ▽ ^ *)

  • 自身技能
    (1)已具备计算机的基本知识
  • 本文简介
    主要讲解:协程的概率,使用方式,协程所用到的api。

协程基本概念和使用

  协程,和线程类似,可以理解成一种轻量级的线程,协程可以仅在编程语言的层面就能实现不同协程之间的切换,这样就提升了并发编程的运行效率。线程是重量级的,需要依靠操作系统的调度才能实现不同线程之间的切换

  • 协程允许我们在单个线程模式下模拟多线程编程的效果,代码执行时的挂起与恢复完全是由编程语言来控制的,和操作系统无关。

下面代码。没有开启线程情况下,理论上的结果是a0a1a2d0d1d2 。使用协程之后,在执行eatA()时,随时可能被挂起转而去执行eatD()方法,执行eatD()方法时,随时可能被挂起转而去执行eatA()方法,这样结果就不确定了。

fun main() {

    fun eatA(){
        print("a0")
        print("a1")
        print("a2")
    }

    fun eatD(){
        print("d0")
        print("d1")
        print("d2")
    }
}

协程使用的基本函数

  先添加协程依赖库

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
  • GlobalScope.launch 函数每次创建都是顶层协程,这种协程的生命周期和应用程序(简称程序)生命周期绑定。即,GlobalScope.launch 函数创建的都是顶层协程,当应用程序结束,协程也就结束了。
  • Thread.sleep() 方法会 阻塞当前的线程,这样运行在该线程下的所有协程都会被阻塞。
  • runBlocking函数 ,会创建一个作用域,但可以保证在协程作用域内所有代码和子协程没有全部执行完之前,一直阻塞当前线程
  • runBlocking和GlobalScope.launch通常用于测试环境,正式环境中使用容易产生一些性能上的问题。
  • aunch函数 ,可以用来创建多个协程。launch函数必须在协程作用域中才能调用,会在当前协程的作用域下创建子协程
  • 子协程 ,特点是如果外层作用域的协程结束了,该作用域下的所有子协程也会一同结束。
  • delay()函数可以让当前协程延迟指定时间后再执行,是非阻塞式的挂起函数,只会挂起当前协程,不影响其他协程的运行。作用范围:只能在协程的作用域或其他挂起函数中调用,自身也是挂起函数。
  • 挂起函数,在函数的关键字fun前加上suspend关键字,那么该函数就是挂起函数。主要作用是:能在协程作用域中被调用,因为协程作用域中无法调用其他普通函数。 挂起函数之间可以互相调用,且无法提供协程作用域。
  • coroutineScope挂起函数它会继承外部的协程作用域并创建一个子作用域,这样就可以给任意挂起函数提供协程作用域了。另外,可以保证在协程作用域内所有代码和子协程没有全部执行完之前,一直阻塞当前线程,只要执行完之后,才运行后面的代码。
  • coroutineScope函数只会阻塞当前协程,即不影响其他协程,也不影响任何线程;runBlocking函数由于会阻塞当前线程,所以可能会造成性能上的问题,coroutineScope函数则不会造成。
  • GlobalScope.launch与runBlocking函数是可以任意地方调用的,coroutineScope函数可以在协程作用域或者挂起函数中调用,而launch函数只能在协程作用域中调用
  • cancel()函数 ,是Job中的函数,用于取消协程。GlobalScope.launch函数和launch函数,都会返回一个Job对象,通过Job对象调用cancel()即可取消协程。
  • CoroutineScope()函数 ,CoroutineScope()函数接收一个Job对象,并返回一个CoroutineScope对象,通过CoroutineScope对象调用launch函数创建一个协程了,所有调用CoroutineScope的launch函数所创建的协程,都关联在Job对象的作用域下,这就可以只调用一次cancel()函数,就将同一作用域内的所有协程全部取消。避免每次创建顶层协程,都需要调用cancel()函数进行取消,不方便后期维护。
  • async函数,必须在协程作用域当中才能调用,它会创建一个新的子协程并返回一个Deferred对象,使用Deferred对象的await()方法就可以得到async函数代码块的执行结果。 调用await()函数时,如果代码没执行完,就会阻塞当前协程,直到获取async函数的执行结果。
  • async函数可以获取到创建的协程的执行结果,而launc函数不能获取创建的协程的执行的结果,因为它的返回值永远是个Job对象。
  • withContext函数必须指定一个线程参数。调用后,立即执行代码块中代码,同时将当前协程阻塞住。当代码块全部执行完,就将最后一行的执行结果作为withContext()函数的返回值返回。
  • 线程参数Dispatchers.Default表示会使用一种默认低并发的线程策略,用于计算密集型任务时,开启过高的并发可能会影响任务运行效率的场景。Dispatchers.IO表示会使用一种较高并发的线程策略,用于大多数时间是在阻塞和等待中,如网络请求。Dispatchers.Main表示不会开启子线程,在android主线程中执行代码,只有在Android项目中使用,纯Kotlin程序使用该参数会出现错误。 我们在主线程里面的协程中处理耗时操作,也可能引发程序错误。
  • suspendCoroutine函数,必须在协程作用域或挂起函数中才能调用,它接收一个Lambda表达式参数,主要作用是将当前协程立即挂起,然后在一个普通的线程中执行Lambda表达式中的代码。Lambda表达式的参数列表上会传入一个Continuation参数,调用它的resume方法或者resumeWithException()可以让协程恢复执行。

示例代码

  开启一个协程例子,如使用GlobalScope.launch函数。这里GlobalScope.launch函数可以创建一个协程作用域,传给launch函数的代码块(Lambda表达式)就在协程中运行的。
  不加Threa.sleep(1000)就会发现,没有日志打印,因为当应用程序运行完时,协程也会结束,导致println(“World!”)未来得及执行。加了之后,进行了延迟,也就让该打印信息执行了。

fun main() {
    GlobalScope.launch { // 协程的生命周期和应用程序生命周期绑定
        println("World!")
    }
    Thread.sleep(1000L) // 阻塞主线程 1 秒钟
 }

  如果程序1s内未执行完,那么就会被强制中断。如下面程序,World!就未打印。

fun main() {
    GlobalScope.launch { 
        println("start")
        delay(1500) 
        println("World!")
    }
    Thread.sleep(1000L) // 阻塞主线程 1 秒钟
}

runBlocking函数使用

fun main() {
    runBlocking {
        println("start")
        delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
        println("World!")
    }
}
//结果
//start
//World!

launch函数使用

runBlocking {
        launch {
            println("A")
            delay(1000)
            println("A end")
        }
        launch {
            println("B")
            delay(1000)
            println("B end")
        }
    }
//    A
//    B
//    A end
//    B end

  coroutineScope,它会继承外部的协程作用域并创建一个子作用域,这样就可以给任意挂起函数提供协程作用域了。另外,可以保证在协程作用域内所有代码和子协程没有全部执行完之前,一直阻塞当前协程,只要执行完之后,才运行后面的代码。

suspend fun eatThing() = coroutineScope {
    launch {
        println("Hello")
        delay(1000)
    }
}

Job与cancel()函数使用

val job = GlobalScope.launch { 
        // 处理具体的逻辑
    }
    job.cancel()

  每次创建顶层协程,都需要在取消的时候调用协程的cancel()方法,这样就不方便后期维护。可以创建Job对象,传递给CoroutineScope()函数,该函数返回一个CoroutineScope对象,通过CoroutineScope对象就可以调用launch函数创建一个协程了。

val job = Job()
    val scope = CoroutineScope(job)
    scope.launch {
        // 处理具体的逻辑
    }
    job.cancel()

//  ret AaBb 
// cost 2028 ms

  调用await()函数时,如果代码没执行完,就会阻塞当前协程,直到获取async函数的执行结果。

// 两个协程串行进行的
    runBlocking {
        val start = System.currentTimeMillis()
        val ret = async {
            delay(1000)
            "A"+"a"
        }.await()
        val ret2 = async {
            delay(1000)
            "B"+"b"
        }.await()
        println(" ret ${ret + ret2} ")
        val end = System.currentTimeMillis()
        println(" cost ${end - start} ms ")
    }

// 优化,将await()函数在协程执行完后,统一调用,这样两个协程就是并行进行的了
    runBlocking {
        val start = System.currentTimeMillis()
        val ret = async {
            delay(1000)
            "A"+"a"
        }
        val ret2 = async {
            delay(1000)
            "B"+"b"
        }
        println(" ret ${ret.await() + ret2.await()} ")
        val end = System.currentTimeMillis()
        println(" cost ${end - start} ms ")
    }

suspendCoroutine函数优化网络请求,下面代码只是示例,无法完整的运行成功

suspend fun request(address: String): String{
    return suspendCoroutine { continuation ->
        sendHttp(address,object : HttpCallbackListener){
            override fun onFinish(response: String){
                continuation.resume(response)
            }
            override fun onError(e: Exception){
                continuation.resumeWithException(e)
            }
        }
    }
}

// 使用
suspend fun getBaiduResponse(){
    try {
        var response = request("https://www.baidu.com")
    }catch (e: Exception){

    }
}



https://www.xamrdz.com/mobile/4mz1959910.html

相关文章: