Kotlin 协程的挂起和阻塞的区别

一,简介

Kotlin协程引入了非常强大的异步编程模型,通过挂起而不是阻塞来实现并发操作。以下是有关Kotlin协程挂起和阻塞的详细介绍:

  1. 挂起(Suspending):
    • 挂起是指一个协程的执行可以在不阻塞线程的情况下暂停和恢复。
    • 挂起函数是一种能够让协程挂起并释放线程的特殊函数,允许其他协程在该协程挂起期间运行。
    • 协程可以在执行IO操作、等待网络请求、休眠或执行任何可能导致阻塞的操作时挂起。
  1. 阻塞:
    • 阻塞是指线程在执行某个操作时被暂停,直到该操作完成,而不能执行其他任务。
    • 在传统的多线程编程中,通常会使用阻塞调用(如Thread.sleep()或等待I/O操作完成),这会导致线程被阻塞,浪费了宝贵的资源。
  1. 协程的非阻塞特性:
    • Kotlin协程通过将任务挂起到后台线程而不阻塞主线程,使得在同一线程上执行多个任务变得更加高效。
    • 由于协程不需要一直占用线程,所以可以运行大量协程而无需创建太多线程。
  1. 使用协程挂起函数:

在Kotlin中,使用suspend关键字声明挂起函数,这允许函数在协程中挂起。

例如,suspend fun fetchData(): String是一个可以在协程中挂起的函数,它可以执行异步操作而不阻塞线程。

  1. 协程调度器:
  • 协程的执行受调度器的管理,调度器负责决定何时挂起和恢复协程,以及在哪个线程上运行它们。
  • 通过使用不同的调度器,可以控制协程的执行方式,例如在主线程、IO线程或自定义线程池中执行。

总之,Kotlin协程的挂起机制允许在不阻塞线程的情况下执行异步任务,这在编写高效且响应式的并发代码方面非常有用。挂起函数使协程可以在等待I/O或执行其他可能导致阻塞的操作时,让出线程,以提高应用程序的性能和响应性。

二,示例

以下是使用Kotlin协程的示例,演示了挂起和阻塞的区别:

首先,确保你的项目中已经引入了Kotlin协程库,以便使用协程。

复制代码
kotlinCopy codeimport kotlinx.coroutines.*
import kotlin.system.measureTimeMillis

// 一个挂起函数,模拟网络请求
suspend fun fetchData(): String {
    delay(1000) // 模拟延迟1秒的网络请求
    return "Data from the network"
}

fun main() = runBlocking {
    // 创建一个协程作用域
    val time = measureTimeMillis {
        val result = async { fetchData() } // 启动一个协程来执行网络请求
        println("Waiting for data...")
        println("Data received: ${result.await()}")
    }
    println("Time taken: $time ms")
}

上述代码中,我们创建了一个挂起函数fetchData(),它模拟了一个网络请求,使用delay()函数来模拟1秒的延迟。在main函数中,我们使用runBlocking创建了一个协程作用域,以便执行协程。然后,我们使用async启动一个协程来执行fetchData()函数。

现在,让我们看看挂起和阻塞的区别:

  1. 挂起:在async中使用await()函数来获取网络请求的结果,但在等待网络请求的过程中,协程会挂起,而不会阻塞整个线程。这意味着其他协程可以在此期间运行,而不会浪费线程资源。
  2. 阻塞:如果我们使用传统的阻塞方式,例如Thread.sleep(1000),线程将被完全阻塞,无法执行其他任务。这会浪费线程资源,并降低应用程序的性能。

总之,使用协程的挂起机制,可以实现非阻塞的并发操作,提高了应用程序的性能和资源利用率。而传统的阻塞方式则会浪费线程资源,导致应用程序的响应性下降。

三,通过Android项目展示挂起和阻塞的区别

在Android项目中演示挂起和阻塞更容易理解

我们之到通过 runBlocking创建一个顶层协程,会阻塞所在的线程;例如我们在主线程使用runBlocking创建一个需要耗时操作的协程;

复制代码
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.ang.rxdemo1.databinding.ActivityCoroutine2Binding
import kotlinx.coroutines.*

class CoroutineActivity2 : AppCompatActivity() {
    lateinit var binding: ActivityCoroutine2Binding;
    private var job: Job? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityCoroutine2Binding.inflate(layoutInflater)
        setContentView(binding.root)


        binding.btnSubmit.setOnClickListener {
            
            runBlocking(Dispatchers.IO + CoroutineName("顶层协程")) {//协程中有耗时操作,需要10S才能执行完成
               Log.d(TAG,"协程开始执行")
               delay(1000.times(10))
               Log.d(TAG,"协程执行完成")
            }
            
        }
    }

xml布局:activity_coroutine2.xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    tools:context=".xiecheng.CoroutineActivity">

    <Button
        android:id="@+id/btn_submit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="执行"/>

</androidx.appcompat.widget.LinearLayoutCompat>

测试连续多次点击"执行"按钮稍等片刻后就会出现ANR导致程序崩溃;这就是runBlocking创建的协程阻塞主线程无法执行其他操作,导致的用户无响应异常的出现;

如果上面代码使用协程挂起函数执行耗时操作,不会阻塞主线程的执行;

复制代码
 binding.btnSubmit.setOnClickListener {
//            runBlocking(Dispatchers.IO + CoroutineName("顶层协程")) {//协程中有耗时操作,需要10S才能执行完成
//                Log.d(TAG,"协程开始执行")
//                delay(1000.times(10))
//                Log.d(TAG,"协程执行完成")
//            }
            val coroutineScope = CoroutineScope(Dispatchers.Main + CoroutineName("协程A"))
            coroutineScope.launch{
                Log.d(TAG,"协程开始执行 ${Thread.currentThread().name} ${coroutineContext[CoroutineName]?.name}")
                delay(10000)//挂起函数,挂起当前协程
                Log.d(TAG,"协程执行完成 ${Thread.currentThread().name} ${coroutineContext[CoroutineName]?.name}")
            }
        }

在多次点击不会阻塞主线,所以也不会出现ANR 异常;

也可以通过如下代码对比挂起和阻塞的区别

阻塞线程:

复制代码
 binding.btnSubmit.setOnClickListener {
            Thread.sleep(100000)
            Log.d(TAG,"协程执行完成 ${Thread.currentThread().name}")

 }

挂起非阻塞线程:

复制代码
binding.btnSubmit.setOnClickListener {
            GlobalScope.launch(Dispatchers.Main + CoroutineName("协程A")) {
                Log.d(TAG,"协程开始执行 ${Thread.currentThread().name} ${coroutineContext[CoroutineName]?.name}")
                delay(10000)//挂起函数,挂起当前协程
                Log.d(TAG,"协程执行完成 ${Thread.currentThread().name} ${coroutineContext[CoroutineName]?.name}")
            }

//            Thread.sleep(100000)
//            Log.d(TAG,"协程执行完成 ${Thread.currentThread().name}")

 }
相关推荐
还鮟1 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡3 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi003 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil4 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你5 小时前
Android View的绘制原理详解
android
移动开发者1号7 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号7 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best12 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk13 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭17 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin