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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ang_qq_252390816

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值