kotlin协程全方位解析

与线程的区别和关系

(1)目的本质上存在差异
线程目的是提高CPU资源使用率,使多个任务得以并行的运行,是为了服务于机器的
协程目的是让多个任务之间更好的协作,主要体现在代码逻辑上,是为了服务于人的
(2)调度上
线程的调度是系统完成的, 一般是抢占式的,根据优先级来分配,是空分复用
协程的调度是开发者指定好的,在不同时期把资源合理地分配给不同任务,是时分复用的
(3)两者的关系
协程并不是取代线程,而且抽象于线程之上,线程是被分割的CPU资源,协程是组织好的代码流程,协程需要线程来承载运行,线程是协程的资源,但协程不会直接使用线程,协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池

基本使用

导入依赖

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

使用GlobalScope开启协程

  GlobalScope.launch {
            Log.d(TAG, Thread.currentThread().name)
        }

我们可以通过log看到,它执行在子线程
在这里插入图片描述
当然我们也可以指定线程,比如让它运行在主线程

 GlobalScope.launch (Dispatchers.Main){
            Log.d(TAG, Thread.currentThread().name)
        }

由此可见,协程可以让我们开启一个线程,是一个线程框架
好了,看到这,相信大家已经知道协程是什么东东了,但是,在协程中,还有一个非常好用的函数是withContext,这个函数可以切换到指定线程,并在闭包中的逻辑执行完后自动把线程切回去继续执行

  CoroutineScope(Dispatchers.Main).launch {
            println(Thread.currentThread().name)
            withContext(Dispatchers.IO) {  //切换到 IO 线程
                Log.d("jayio",Thread.currentThread().name)
            }
           Log.d("jaymain",Thread.currentThread().name)		//主线程更新
        }

在这里插入图片描述
由于可以自动切回来,我们甚至可以把 withContext 放进一个单独的函数里面

    suspend fun getCoroutine(): Boolean = withContext(Dispatchers.IO) {
        //...执行操作
    }

例如,我们要在网络上下载图片然后显示出来,可以这样做

    private suspend fun getImage(): Bitmap = withContext(Dispatchers.IO) {
        OkHttpClient().newCall(
            Request.Builder()
                .url(url)
                .get()
                .build()
        )
            .execute().body?.byteStream().use {
                BitmapFactory.decodeStream(it)
            }
    }
  CoroutineScope(Dispatchers.Main).launch {
            img.setImageBitmap(getImage())
        }

关键字 —> suspend

协程指的就是 launch 中的代码,其实挂起的对象就是协程。当执行 launch 时,在执行到某一个 suspend 函数时,这个协程就会被挂起。让时间静止,兵分两路,来看一下到底是怎么回事,这两路分别是协程和线程(UI线程)。如果你某个函数比较耗时,也就是需要等,就可以把它写成 suspend 函数。再看上面的例子,当主线程执行getImage 时,会跳出协程。总而言之,协程在执行到有 suspend 的时候就会被挂起,而这个挂起,则就是切个线程;只不过挂起的执行完后会重新切回他原来的线程,这个切回来的动作,在 Kotlin 中叫做 resume(恢复)
当然并不是只有 withContext 来辅助我们实现自定义的函数,如 delay ,他的作用是等一段时间后在继续往下执行代码

  suspend fun waitTime() {
        delay(5) //挂起
    }

suspend 的意义
其实 suspend 关键字没有实际的挂起,因为他本来就不是用来操作挂起的。也就是说切线程依赖的是函数中的代码,而不是这个关键字,这个关键字只是用来提醒,只有一个效果,那就是限制此函数只能在协程中被调用,如果在非协程中调用,则编译不会通过
当然,我们也可以将回调写成挂起函数

    private suspend fun getImagenew() = suspendCoroutine<Bitmap> { continuation ->
        OkHttpClient().newCall(
            Request.Builder()
                .url(url)
                .get()
                .build()
        )
            .enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    continuation.resumeWithException(e)
                }

                override fun onResponse(call: Call, response: Response) {
                    continuation.resume(
                        response.body?.byteStream().use { BitmapFactory.decodeStream(it) })
                }
            })
    }

非阻塞式挂起

非阻塞式挂起并没有限定在一个线程中,因为挂起本来就涉及到多个线程。主线程执行的时候遇到耗时任务,然后将耗时任务挂起,这时主线程就自由了,可以继续做别的事了。所以非阻塞式挂起其实就是在讲 协程在挂起的时候切换线程这件事

协程的具体使用

delay

   fun sayHellow(){
       GlobalScope.launch {
           delay(1000L) //协程挂起,阻塞1秒
           Log.d("jaydelay"," world")
       }
       Log.d("jay","hellow")  //协程挂起时,主线程继续执行
       Thread.sleep(2000L)//延时,保证主线程存活
   }

在这里插入图片描述
runBlocking(阻塞式)

    fun sayHellow() {
        runBlocking {
        //此处还是主线程
            GlobalScope.launch {
                delay(1000L)
            }
            delay(2000L) //延时,保证主线程存活,其实在runBlocking里面不需要这句也行
        }
    }

等待一个任务

 suspend fun sayHellow() {
        val job = GlobalScope.launch {
            delay(1000L)
        }
        job.join() //等待子线程执行结束
    }

该​方法被 suspend 修饰了,因为 join 方法被 suspend 修饰过,suspend 本身不会挂起,挂起是因为join 内部有挂起的代码,suspend 只是一个提示,只是这个提示必须写

结构化并发

    fun sayHellow() = runBlocking {
        //开始执行主协程
        launch {
            delay(1000L)
            Log.d("ha1", " world")
        }//在最后并没有让主线程等待,也没有调用 join,依然会打印出 world
        Log.d("ha2", "Hellow")
    }

在这里插入图片描述
在 runBlocking 内的每个协程构建器中都将 CoruntineScope 的实例添加到代码块的作用域中。我们可以在这个作用域中启动协程而无需显示调用 join。因为外部协程(runBlocking)直到在其作用域中启动的所有协程执行完毕后才会结束

猜你喜欢

转载自blog.csdn.net/qq_45485851/article/details/108070219