Kotlin协程引入了非常强大的异步编程模型,通过挂起而不是阻塞来实现并发操作。以下是有关Kotlin协程挂起和阻塞的详细介绍:
在Kotlin中,使用suspend关键字声明挂起函数,这允许函数在协程中挂起。
例如,suspend fun fetchData(): String是一个可以在协程中挂起的函数,它可以执行异步操作而不阻塞线程。
总之,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()函数。
现在,让我们看看挂起和阻塞的区别:
总之,使用协程的挂起机制,可以实现非阻塞的并发操作,提高了应用程序的性能和资源利用率。而传统的阻塞方式则会浪费线程资源,导致应用程序的响应性下降。
在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}")
-
- }