Kotlin 协程coroutine

koltin 协程的定义

官方描述:协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。
简单理解:一般程序会有一个主进程,主进程中可能含有多个线程。而协程,是线程中的,也就是说一个线程中可能包含多个协程,协程与协程之间是可以嵌套的。
精确奥义:在Kotlin里协程就是由官方提供的一套线程API

       //java里Excutor
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(new Runnable() {
            @Override
            public void run() {

            }
        });
     //java里线程
        new Thread(new Runnable() {
            @Override
            public void run() {

            }
        }).start();
     //Koltin里协程
        launch{

          }

koltin 协程的作用是什么

那既然java里有Thread 和Executor ,Android 里又增加了Handler和AsyncTask,并且现在还有了RxJava,那么协程的优势在呢?其实协程和以上所说的没有本质区别,但是借助了Kotlin的语言优势,更方便的实现对线程的操作,仅此而已。
协程的作用简单来说,有以下两个方面

  • 协程通过替代回调(callback)来简化异步代码。
  • 通过提升 CPU 利用率,减少线程切换进而提升程序运行效率。

案例1

  //非阻塞式挂起,用看似同步的方式写出异步的代码
        var userName = getUserInfo() //网路请求
        userTv?.text = userName.toString()

上面的代码是不是看起来很清爽,在java里,实现网络请求,通常是用回调的,而我们对于回调也是早就习以为常,通常也是在回调方法里进行页面数据更新,当然这个也是根据不同的网络请求框架的。但是用了协成,就可以这样,上下顺序排列,简直完美。协程改变了并发任务的操作难度,更容易coder操作

案例2

再比如现在还有一个操作,根据业务需要,我们需要进行两个网络请求,在这两个网络请求都返回数据后,我们对数据进行加工组装,展示出来。
对于我们来说,这两个网络请求是没有关联的,可以异步发起,但是很可能受到网络框架影响和知识影响,我们给写成了同步的,即:先进行网络请求1,在网络请求1成功的回到里拿到数据,在进行网络请求2,在网络请求2成功能的回调里进行数据汇总加工,由原来的并行变成串行,这种就导致页面加载速度降低了一倍但是,有了协程,就可以这样:


        //协程
     launch(Dispatchers.Main){
        var userInfo=async{getUserInfo()}   //网络请求
        var userToken=async{getUserToken()} //网络请求
        var userData=unDateUI(userInfo,userToken) //显示

}

协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。协程很重要的一点就是当它挂起的时候,它不会阻塞其他线程。

协程的特性

  • 可控制:协程能做到可被控制的发起子任务(协程的启动和停止都由代码控制,不像 java)
  • 轻量级:协程非常小,占用资源比线程还少
  • 语法糖:使多任务或多线程切换不再使用回调语法

koltin 协程要怎么用

当我们知道了协程的定义后,就可以了解到协成的使用场景,在需要执行耗时操作,可能引起阻塞时,(Android系统为了保证界面的流畅和及时响应用户的输入事件,主线程需要保持每16ms一次的刷新(调用 onDraw()函数),所以不能在主线程中做耗时的操作(比如 读写数据库,读写文件,做网络请求,解析较大的 Json 文件,处理较大的 list 数据)。 )就可以由原来的java子线程换成Kotlin协程,这样程序执行更加顺畅,值得注意的是,协成并不一定是在子线程的才可以用的,在主线程中也是可以实现的。

原则 只要是耗时或者需要等待的操作,都可以使用协程

需要使用到的库,要提起前导入

//Kotlin协程依赖
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
//切换到后台执行IO操作
   launch(Dispatchers.IO) {
            saveToDisk(data)
        }
//切換到主线程
   launch(Dispatchers.Main) {
            undateUi(data)
        }
**注意,launch 函数里一连串的代码片段统称为协程**

如果只用launch函数,那么协程并不能做太多的事情,仅仅是切换或者指定线程,所以还要配合withContext()
withContext()函数可以指定线程来执行代码,并且执行完毕后再切换回来,继续执行原先线程里的后续代码。

    //withContext()
       launch(Dispatchers.Main){
         var image=withContext(Dispatchers.IO){
               saveImageToDisk(data)
           }
     userImage.setImageBitmap(image)
}

对于上面代码,可以解释成,在主线程里,我们启用了协程,并且指定在IO线程里操作,当操作完成后,会自动切换回主线程,进行页面的刷新操作

由于由了自动切换的操作,所以在处理并发任务的时候,我们就可以优雅很多,甚至可以把WithContext单独抽取出来,放到函数里,包裹着实际操作的耗时代码,例如;

      launch(Dispatchers.Main){
         var image= saveImageToDisk(data)
         userImage.setImageBitmap(image)
}
      suspend fun saveImageToDisk(data:String){
     withContext(Dispatchers.IO){
          save()
  }
}

但是如果直接这样写,是会报错的,因为withContext方法需要在协程里面被调用,所以saveImageToDisk函数前面要加上suspend 关键字,提醒调用者,我是一个耗时操作,我要在协程里被调用。
suspend是kotlin协程中一个非常重要的关键字,当执行有被suspend关键字修饰的函数式,线程会被挂起,并且这个挂起是非阻塞式的,不会影响后面的继续执行。
那么这个挂起的是什么?线程还是函数?
都不是,挂起的是协程,也就是launch包裹的代码片段,也就是说,这个线程跟这个协程从此脱离了,不是线程暂停了。
那么如果协程被挂起了,后续操作是怎么进行的呢?
如果这个协程所在的线程是子线程,那么协程被挂起后,子线程继续执行自己的任务,要么继续,要么等待回收,如果这个协程所在的线程是主线程,那么协程被挂起后,主线程则是继续刷新页面。
以上是对于线程来说的,那么对于协程来说呢?
对于协程来说,当执行到suspend挂起函数时,协程的代码内容由withContext 指定的线程继续执行,执行完毕后在切换到原来线程,继续执行。
可能有点绕,这点需要好好消化下。
另外补充一下
如果需要指定协程运行的线程,就需要指定Dispatchers ,常用的有三种:

  • Dispatchers.Main:Android中的主线程,可以直接操作UI
  • Dispatchers.IO:针对磁盘和网络IO进行了优化,适合IO密集型的任务,比如:读写文件,操作数据库以及网络请求
  • Dispatchers.Default:适合CPU密集型的任务,比如解析JSON文件,排序一个较大的list
    注意BaseActivity要继承 CoroutineScope by MainScope(),launch才能被识别

所谓的挂起,其实本质上还是切换了一个线程
到这里,我们就可以来总结下
协程,本质上就是一个线程切换框架
挂起,本质上是就是切换线程,
协程的非阻塞式,和java里线程的非阻塞是一样,只不过更方便而已。

好了,今天就学习到这里,协程还有很多知识需要掌握,大家good good study and day day up!

你可能感兴趣的:(Kotlin 协程coroutine)