Kotlin1.3版本的协程

作者: Taonce | 来源:发表于2018-11-19 21:43 被阅读10次

    协程的开发人员 Roman Elizarov 是这样描述协程的:协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。

    使用Kotlin的协程

    添加依赖:coroutines库在Kotlin1.3版本的时候已经升级为正式版,命名为1.0.0,目前最新版是1.0.1

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'
    

    也可以添加Android的依赖:

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'
    

    开启协程的方法:

    1. GlobalScope.launch

        fun main(args: Array<String>) {
            GlobalScope.launch {
                // 非阻塞线程1s
                delay(1000L)
                println("This is a coroutines ${TimeUtil.getTimeDetail()}")
            }
            // 阻塞线程2s,保证JVM存活,协程可正常执行完
            Thread.sleep(2000L)
            println("main end ${TimeUtil.getTimeDetail()}")
        }
        
        // 输出
        // This is a coroutines 10:49:58
        // main end 10:49:59
      

      在线程环境中可直接使用CoroutineScope.launch启动一个新的协程,它的参数有如下三个,分别为:

      • context: CoroutineContext = EmptyCoroutineContext:协程上下文

      • start: CoroutineStart = CoroutineStart.DEFAULT:启动模式,默认是DEAFAULT,也就是创建就启动;还有一个是LAZY,意思是等你需要它的时候,再调用启动。在Kotlin 1.3版本中,还有ATOMICUNDISPATCHED两个额外的模式,但是现在还是实验版,这里不多介绍。

      • block: suspend CoroutineScope.() -> Unit:闭包参数,定义协程内需要执行的操作。

        返回值为Job对象。Job有如下几个重要的方法,分别为:

      1. job.start()可配合LAZY启动一个协程

        fun main(args: Array<String>){
            val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
                println("this is a job")
            }
            job.start()
        
            Thread.sleep(1000L)
        }
        
        // 输出
        // this is a job
        
      2. job.join()等待协程执行完毕

        suspend fun main(args: Array<String>){
            val job = GlobalScope.launch {
                println("this is a job")
            }
            job.join()
        }
        
        // 输出
        // this is a job
        

        注意:join()函数是一个挂起函数,所有main必须被suspend修饰。

      3. job.cancel()取消一个协程

        suspend fun main(args: Array<String>){
            val job = GlobalScope.launch {
                println("this is a job")
            }
            job.cancel()
            job.join()
        }
        
        // 无输出,协程被取消了
        
      4. job.cancelAndJoin()等待协程执行完毕然后再取消

        suspend fun main(args: Array<String>){
            val job = GlobalScope.launch {
                println("this is a job")
            }
            job.cancelAndJoin()
        }
        
        // 输出
        // this is a job
        

        job.cancelAndJoin()也是一个挂起函数。

    从上面的代码和输出可以看到,协程中的输出和main中的输出只相差了1s,也就说明了为什么delay(1000L)是非阻塞的。delay()函数类似于Thread.sleep(),但是它不阻塞(non-blocking)线程,它是一个被suspend修饰的挂起函数,挂起函数只能被挂起函数调用或协程中调用。


    1. GlobalScope.async

      suspend fun main(args: Array<String>) {
          val deferred = GlobalScope.async {
              delay(1000L)
              println("This is async ${TimeUtil.getTimeDetail()}")
              return@async "taonce"
          }
          println("main start ${TimeUtil.getTimeDetail()}")
          val result = deferred.await()
          println("async result is $result")
          println("main end ${TimeUtil.getTimeDetail()}")
      }
      // 输出
      // main start 15:27:19:668
      // This is async 15:27:20:652
      // async result is taonce
      // main end 15:27:20:657
      

      asynclaunch参数是一模一样的,不同的是async返回的是Deferred对象,它继承了Job接口,所以说Job有的它都有,并且还额外增加了一个方法:

      public suspend fun await(): T这个方法接收的是async闭包中返回的值。如果闭包中需要返回一个值那么我们就需要考虑用async了。


    2. runBlocking

      runBlocking的最大特点就是它的delay()可以阻塞当前的线程,和Thread.sleep()有着相同的效果,看下面代码:

        fun main(args: Array<String>) {
            runBlocking {
                // 阻塞1s
                delay(1000L)
                println("This is a coroutines ${TimeUtil.getTimeDetail()}")
            }
            // 阻塞2s
            Thread.sleep(2000L)
            println("main end ${TimeUtil.getTimeDetail()}")
        }
        // 输出
        // This is a coroutines 11:00:51
        // main end 11:00:53
      

      乍一看,这不和上面的launch一样么,只是替换成了runBlocking而已,但是大家可以仔细看一看输出,两次输出之间的时间差为2s,也就是说runBlocking中的delay(1000L)确确实实的把当前线程给阻塞了,让当前线程也睡眠了1s。而通过launch开启的协程中却不是如此,这就是二者的不同之处之一。

      所以我们一般用runBlocking来桥接普通阻塞代码和挂起风格的非阻塞代码,在runBlocking闭包里面启动另外的协程。


    实际运用

    就拿最常用的网络请求为例吧,如果我们不用Kotlin的协程来实现,我们可以用ThreadRxJava等等很多种方法,那么我们来看看协程是怎么实现的吧。

    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            coroutine.setOnClickListener { click() }
        }
    
        private fun click() = runBlocking {
            GlobalScope.launch(Dispatchers.Main) {
                coroutine.text = GlobalScope.async(Dispatchers.IO) {
                    // 比如进行了网络请求
                    // 放回了请求后的结构
                    return@async "main"
                }.await()
            }
        }
    }
    

    Async()在IO线程中去执行网络请求,然后通过await()返回请求结果,用launch()在主线程中更新UI就行了。

    其中我们用了Dispatchers来指定协程所在的线程,目前Dispatchers有三种:MainIODefault

    • Default:如果没有指定具体的Dispatchers都会使用默认的,它使用的是最大的线程数。
    • IO:用来调度阻塞的协程
    • Main:就是常说的UI线程了,是Android特有的。

    协程的知识点还有很多很多,这里只能带大家简单了解一下稳定版的coroutines,想要了解更多大家可以去Kotlin中文文档详细学习。下面贴出中文文档地址。

    https://www.kotlincn.net/docs/reference/coroutines/basics.html


    写在最后

    每个人不是天生就强大,你若不努力,如何证明自己,加油!

    Thank You!

    --Taonce

    如果你觉得这篇文章对你有所帮助,那么就动动小手指,长按下方的二维码,关注一波吧~~非常期待大家的加入

    专注Kotlin和Android知识的公众号

    相关文章

      网友评论

        本文标题:Kotlin1.3版本的协程

        本文链接:https://www.haomeiwen.com/subject/xhqzfqtx.html