Android面试题之Kotlin 协程的挂起、执行和恢复过程

简介: 了解Kotlin协程的挂起、执行和恢复机制。挂起时,状态和上下文(局部变量、调用栈、调度器等)被保存;挂起点通过`Continuation`对象处理,释放线程控制权。当恢复条件满足,调度器重新分配线程,调用`resumeWith`恢复执行。关注公众号“AntDream”获取更多并发知识。

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点

协程的挂起、执行和恢复过程到底是怎么样的?

协程(Coroutine)的挂起和恢复机制是其高效管理并发性的核心。这些过程涉及多个关键步骤,包括状态和上下文的保存、释放线程控制权、以及恢复时的通知等。
我们来详细讲解一下这些机制。

1. 协程挂起和恢复时保存的状态和上下文

1.1 状态信息

协程的状态信息主要包括:

  • 局部变量:函数当前执行到的位置以及所有局部变量的值。
  • 挂起点:协程挂起的位置,这个位置通常是代码中的一个挂起点(suspend函数)。
  • 调用栈:它对应当前执行的协程堆栈帧,可以看作是对函数调用链的保存。

1.2 上下文信息

协程的上下文信息通常包括:

  • 调度器:即协程运行的调度器(例如 Dispatchers.IO, Dispatchers.MainDispatchers.Default)。
  • 异步工作器:包含了协程的执行环境和工作状态。
  • 取消状态:协程是否被取消或处于取消状态。

2. 状态与上下文的保存形式

2.1 协程堆栈帧

协程在挂起时,会将当前的堆栈帧转换为对象并存储在堆中。这个对象包含了所有当前帧的局部变量、挂起点以及其他必要信息。恢复时,这个对象重新转换为堆栈帧并继续执行。

2.2 Continuation

Kotlin中的挂起函数实质上会被编译器转换成带有回调的 Continuation 对象。该对象包含两个主要部分:

  • 上下文(Continuation Context):绑定的执行环境。
  • 恢复逻辑(Resume Logic):保存和处理挂起点的逻辑。
interface Continuation<in T> {
    val context: CoroutineContext
    fun resumeWith(result: Result<T>)
}

3. 协程挂起后切换到其他线程执行

当协程遇到挂起点(如 delay, awaitsuspend 函数)时,它会触发挂起机制,具体步骤如下:

3.1 挂起点的处理

当协程在挂起点被挂起时,当前函数状态和局部变量会被保存到 Continuation 对象中。
挂起函数会将 Continuation 对象传递给协程的调度器。

3.2 挂起协程和释放线程

调度器会暂停当前协程的执行,把线程控制权交给调度器管理的线程池或其他任务,从而释放当前线程。

4. 切换到其他线程执行

4.1 恢复协程条件满足

当挂起的条件满足(例如 delay 到期,或者异步任务完成),调度器会收到执行恢复逻辑的通知。

4.2 调度器分配新的线程运行

调度器会将保存的 Continuation 对象重新分配给线程池中的某个线程,调用 resumeWith 方法恢复协程:

continuation.resumeWith(Result.success(result))

这时,协程会恢复到挂起点之后的代码继续正常执行。

5. 协程在其他线程执行完后的通知机制

5.1 异步任务完成通知

当协程在新的线程中执行完任务(比如完成网络请求等异步任务)时,执行环境会调用 ContinuationresumeWith 方法:

continuation.resumeWith(Result.success(result))

5.2 通知调度器

resumeWith 方法会触发协程恢复处理,同时通知调度器该协程已准备好继续执行。

6. 调度器恢复协程到原线程

6.1 检查恢复环境

调度器检查协程需要恢复的环境,特别是上下文中的线程调度信息。如果协程需要恢复到特定线程(例如主线程),调度器会安排该任务。

6.2 分配线程

调度器找到或分配合适的线程,根据协程上下文完成恢复调度。典型的调度器如 Dispatchers.Main 或自定义调度器负责将任务放回特定线程运行。

6.3 恢复执行

调度器调用 Continuationresume 方法,将保存的上下文和状态恢复到协程堆栈:

continuation.resume(result)

然后,协程在新的或原来的线程上恢复执行挂起点之后的代码。
以下是一个简单的示例,展示了协程如何在挂起后切换到不同线程并恢复到主线程:

import kotlinx.coroutines.*

fun main() = runBlocking {
    withContext(Dispatchers.Main) {
        println("Running on main thread: ${Thread.currentThread().name}")

        withContext(Dispatchers.IO) {
            println("Switching to IO thread: ${Thread.currentThread().name}")
            delay(1000) // 挂起点
            println("Resuming on IO thread: ${Thread.currentThread().name}")
        }

        println("Back to main thread: ${Thread.currentThread().name}")
    }
}

在这个例子中:

  • withContext(Dispatchers.Main) 确保代码一开始运行在主线程。
  • withContext(Dispatchers.IO) 切换到 I/O 线程,执行 delay 挂起。
  • 挂起后,协程会保存当前状态和上下文,并交由 Dispatchers.IO 管理。
  • 延时结束后,Dispatchers.IO 恢复协程执行。
  • withContext(Dispatchers.Main) 之后的代码切换回主线程,保证恢复到原来的执行环境。

总结

综上所述,Kotlin 协程在挂起和恢复过程中,通过调度器实现线程的切换和任务调度:

  1. 挂起点保存当前状态由Continuation管理。
  2. 调度器控制挂起后的线程控制权交还。
  3. 恢复条件满足时,调度器分配新线程,调用resumeWith方法恢复协程。
  4. 调度器检查恢复环境,分配合适线程并调用resume方法继续执行。

欢迎关注我的公众号AntDream查看更多精彩文章!

目录
相关文章
|
1天前
|
安全 Java Android开发
探索Android应用开发中的Kotlin语言
【7月更文挑战第19天】在移动应用开发的浩瀚宇宙中,Kotlin这颗新星以其简洁、安全与现代化的特性,正迅速在Android开发者之间获得青睐。从基本的语法结构到高级的编程技巧,本文将引导读者穿梭于Kotlin的世界,揭示其如何优化Android应用的开发流程并提升代码的可读性与维护性。我们将一起探究Kotlin的核心概念,包括它的数据类型、类和接口、可见性修饰符以及高阶函数等特性,并了解这些特性是如何在实际项目中得以应用的。无论你是刚入门的新手还是寻求进阶的开发者,这篇文章都将为你提供有价值的见解和实践指导。
|
3天前
|
SQL 安全 Java
Android经典面试题之Kotlin中object关键字实现的是什么类型的单例模式?原理是什么?怎么实现双重检验锁单例模式?
Kotlin 单例模式概览 在 Kotlin 中,`object` 关键字轻松实现单例,提供线程安全的“饿汉式”单例。例如: 要延迟初始化,可使用 `companion object` 和 `lazy` 委托: 对于参数化的线程安全单例,结合 `@Volatile` 和 `synchronized`
14 6
|
1天前
|
存储 前端开发 测试技术
Android Kotlin中使用 LiveData、ViewModel快速实现MVVM模式
使用Kotlin实现MVVM模式是Android开发的现代实践。该模式分离UI和业务逻辑,借助LiveData、ViewModel和DataBinding增强代码可维护性。步骤包括创建Model层处理数据,ViewModel层作为数据桥梁,以及View层展示UI。添加相关依赖后,Model类存储数据,ViewModel类通过LiveData管理变化,而View层使用DataBinding实时更新UI。这种架构提升代码可测试性和模块化。
11 2
|
3天前
|
Android开发 Kotlin
Android经典面试题之Kotlin中Lambda表达式有哪些用法
Kotlin的Lambda表达式是匿名函数的简洁形式,常用于集合操作和高阶函数。基本语法是`{参数 -&gt; 表达式}`。例如,`{a, b -&gt; a + b}`是一个加法lambda。它们可在`map`、`filter`等函数中使用,也可作为参数传递。单参数时可使用`it`关键字,如`list.map { it * 2 }`。类型推断简化了类型声明。
7 0
|
22天前
|
Go Python
使用python实现一个用户态协程
【6月更文挑战第28天】本文探讨了如何在Python中实现类似Golang中协程(goroutines)和通道(channels)的概念。文章最后提到了`wait_for`函数在处理超时和取消操作中的作
23 1
使用python实现一个用户态协程
|
5天前
|
数据库 开发者 Python
实战指南:用Python协程与异步函数优化高性能Web应用
【7月更文挑战第15天】Python的协程与异步函数优化Web性能,通过非阻塞I/O提升并发处理能力。使用aiohttp库构建异步服务器,示例代码展示如何处理GET请求。异步处理减少资源消耗,提高响应速度和吞吐量,适用于高并发场景。掌握这项技术对提升Web应用性能至关重要。
28 10
|
5天前
|
数据处理 Python
深入探索:Python中的并发编程新纪元——协程与异步函数解析
【7月更文挑战第15天】Python 3.5+引入的协程和异步函数革新了并发编程。协程,轻量级线程,由程序控制切换,降低开销。异步函数是协程的高级形式,允许等待异步操作。通过`asyncio`库,如示例所示,能并发执行任务,提高I/O密集型任务效率,实现并发而非并行,优化CPU利用率。理解和掌握这些工具对于构建高效网络应用至关重要。
19 6
|
5天前
|
大数据 数据处理 API
性能飞跃:Python协程与异步函数在数据处理中的高效应用
【7月更文挑战第15天】在大数据时代,Python的协程和异步函数解决了同步编程的性能瓶颈问题。同步编程在处理I/O密集型任务时效率低下,而Python的`asyncio`库支持的异步编程利用协程实现并发,通过`async def`和`await`避免了不必要的等待,提升了CPU利用率。例如,从多个API获取数据,异步方式使用`aiohttp`并发请求,显著提高了效率。掌握异步编程对于高效处理大规模数据至关重要。
16 4
|
5天前
|
设计模式 机器学习/深度学习 测试技术
设计模式转型:从传统同步到Python协程异步编程的实践与思考
【7月更文挑战第15天】探索从同步到Python协程异步编程的转变,异步处理I/O密集型任务提升效率。async/await关键词定义异步函数,asyncio库管理事件循环。面对挑战,如思维转变、错误处理和调试,可通过逐步迁移、学习资源、编写测试和使用辅助库来适应。通过实践和学习,开发者能有效优化性能和响应速度。
22 3
|
5天前
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
【7月更文挑战第15天】Python异步编程借助协程和async/await提升并发性能,减少资源消耗。协程(async def)轻量级、用户态,便于控制。事件循环,如`asyncio.get_event_loop()`,调度任务执行。异步函数内的await关键词用于协程间切换。回调和Future对象简化异步结果处理。理解这些概念能写出高效、易维护的异步代码。
12 2