什么是 Kotlin 协程?一篇入门 – wiki基地


Kotlin 协程入门:告别回调地狱,拥抱简洁异步编程

在现代软件开发中,尤其是在需要处理网络请求、数据库操作、文件读写或者任何可能耗时阻塞主线程的任务时,异步编程变得至关重要。传统的同步编程方式会导致程序“卡死”,用户界面无响应,用户体验极差。为了解决这个问题,开发者们探索了多种异步编程模型,例如回调、Future/Promise、响应式编程(如 RxJava)。然而,这些模型往往会引入额外的复杂性,比如臭名昭著的“回调地狱”(Callback Hell),或者使得代码的逻辑流程变得难以理解和维护。

Kotlin 协程(Coroutines)的出现,为 Kotlin 语言带来了解决异步编程问题的一种全新且更加优雅的方式。它允许你以几乎像编写同步代码一样的方式来编写异步、非阻塞的代码,极大地提高了代码的可读性和可维护性。

本文将带你深入了解 Kotlin 协程,从为什么需要它,到它的核心概念、工作原理以及如何使用它来构建高效的应用程序。

为什么需要异步编程?传统方式的困境

想象一下,你的应用程序需要从网络上下载一张图片,或者从数据库中读取大量数据。如果直接在主线程(例如 Android 中的 UI 线程)执行这些操作,由于这些操作可能需要几十甚至几百毫秒,程序会暂停执行,直到操作完成。在这段时间里,用户界面会冻结,用户点击按钮没有反应,这就是所谓的“阻塞”。

为了避免阻塞主线程,我们需要将这些耗时任务放到后台线程中执行。这引出了传统的异步编程方式:

  1. 使用线程(Threads):

    • 我们可以创建新的线程来执行耗时任务。任务完成后,如果需要更新 UI,我们必须小心地切换回主线程(例如在 Android 中使用 HandlerrunOnUiThread)。
    • 问题: 线程是操作系统级别的资源,创建和销毁线程开销较大。管理大量线程会变得复杂,容易引发线程同步问题(如竞态条件、死锁),而且调试困难。
  2. 使用回调(Callbacks):

    • 定义一个接口或 Lambda 表达式,当异步操作完成后,调用这个回调函数来处理结果。
    • 问题: 当有多个相互依赖的异步操作时,会出现回调嵌套,形成“回调地狱”,代码层层缩进,逻辑难以追踪。错误处理也分散在各个回调中,不够集中。
  3. 使用 Futures/Promises:

    • 代表一个未来会得到的结果。异步操作返回一个 Future/Promise 对象,你可以注册一个回调来处理结果或错误。
    • 问题: 链式调用虽然比纯回调有所改进,但在处理复杂的控制流(如条件分支、循环)时仍然不够直观。
  4. 使用响应式编程(Reactive Programming,如 RxJava/Flow):

    • 基于数据流和变化的编程范式。通过操作 Observables 或 Flows 来处理异步事件序列。
    • 问题: 功能强大,但学习曲线陡峭,概念较多(Observables, Operators, Schedulers 等)。对于简单的异步任务,引入整个响应式库可能显得有些“重”。

这些传统方式都试图解决异步问题,但在不同的层面上存在复杂性、可读性或资源消耗的问题。Kotlin 协程旨在提供一种更轻量级、更易于理解和使用的异步编程解决方案。

什么是 Kotlin 协程?

简单来说,Kotlin 协程是一种轻量级的并发框架,它允许你编写非阻塞的、异步的代码,而代码看起来却像同步执行的一样。

理解协程的关键在于“轻量级”和“非阻塞”。

  • 轻量级: 协程不是操作系统线程。一个操作系统线程可以同时运行成千上万个协程。协程的创建和切换开销比线程小得多。它们在用户空间进行调度,而不是由操作系统内核调度。
  • 非阻塞: 协程的核心能力在于它能够“暂停”(suspend)自身的执行,而不阻塞它所在的线程,并在稍后从暂停的地方“恢复”(resume)执行。

正是这种“暂停”和“恢复”的能力,让协程能够实现异步操作。当一个协程遇到一个耗时操作(例如网络请求)时,它会暂停执行,释放其占用的线程,让线程可以去执行其他任务或协程。当耗时操作完成后,协程会在某个线程上恢复执行,就像它从未暂停过一样。

这与线程的阻塞行为形成鲜明对比:当一个线程遇到阻塞操作时,它会一直等待直到操作完成,期间不能执行任何其他任务,并且一直占用着操作系统资源。

协程通过编译器转换和运行时库的支持来实现这种暂停和恢复的能力。

核心概念:Suspending Functions

Kotlin 协程的核心构建块是 暂停函数(Suspending Functions)

一个暂停函数是一个可以在执行过程中暂停自身,并在稍后恢复执行的函数。它只能从另一个暂停函数或一个协程构建器(Coroutine Builder)中调用。

在 Kotlin 中,通过在函数声明前加上 suspend 关键字来标记一个暂停函数:

“`kotlin
suspend fun fetchData(): String {
// 模拟一个耗时的网络请求
delay(1000) // delay 是一个暂停函数,它会暂停协程,但不阻塞线程
return “Data from network”
}

suspend fun processData(data: String): String {
// 模拟一个耗时的数据处理
delay(500)
return “Processed: $data”
}
“`

在上面的例子中,fetchDataprocessData 都是暂停函数。delay(1000) 是一个由协程库提供的暂停函数,它会暂停当前协程执行 1000 毫秒,而不会阻塞当前所在的线程。

你可以像调用普通函数一样调用暂停函数,但前提是你在协程的环境中:

“`kotlin
suspend fun performOperation() {
val data = fetchData() // 调用暂停函数
val processed = processData(data) // 调用另一个暂停函数
println(processed)
}

// performOperation() 不能直接在普通函数中调用,必须在一个协程中。
“`

编译器会特殊处理 suspend 函数。它会将暂停函数转换为一个带有额外参数(用于处理状态和恢复点)的状态机。当协程执行到 delay() 或其他暂停点时,状态机会记录当前的状态,然后函数返回。当异步操作完成后,状态机可以根据记录的状态从暂停点继续执行。这使得我们可以用顺序的方式编写异步逻辑。

启动协程:协程构建器 (Coroutine Builders)

暂停函数不能直接在常规的、非协程的代码中调用(例如,不能直接在 main 函数或一个普通的类方法中调用)。你需要一个入口点来启动协程,这就是协程构建器。

Kotlin 协程库提供了一些标准的构建器:

  1. runBlocking:

    • 这是一个特殊的构建器,主要用于连接阻塞世界(如 main 函数或单元测试)和非阻塞的协程世界。
    • 它会启动一个协程并在当前线程上运行它,并且阻塞当前线程直到协程执行完毕。
    • 注意: runBlocking 应该谨慎使用,因为它会阻塞线程。它不适合在应用程序的 UI 线程或需要响应性的地方使用。

    “`kotlin
    import kotlinx.coroutines.*

    fun main() = runBlocking { // this: CoroutineScope
    println(“Start of main”)
    // 在 runBlocking 协程中调用暂停函数
    val data = fetchData()
    println(“Fetched: $data”)
    val processed = processData(data)
    println(processed)
    println(“End of main”)
    }
    “`

    输出:
    Start of main
    Fetched: Data from network
    Processed: Processed: Data from network
    End of main

    尽管 fetchDataprocessData 内部有 delayrunBlocking 使它们看起来像同步执行一样,按顺序输出。但请记住,runBlocking 本身阻塞了 main 线程。

  2. launch:

    • 用于启动一个新的协程,执行一个任务,并且不期望返回结果
    • 它返回一个 Job 对象,代表了协程的生命周期。你可以使用 Job 来等待协程完成 (join()) 或取消协程 (cancel())。
    • launch 是“fire-and-forget”式的,通常用于需要在后台执行但不需要直接返回结果的任务,例如更新 UI 或日志记录。

    “`kotlin
    import kotlinx.coroutines.*

    fun main() = runBlocking { // this: CoroutineScope
    println(“Main starts”)

    // 启动一个协程,执行任务
    val job: Job = launch {
        println("Coroutine started")
        delay(1000)
        println("Coroutine finished")
    }
    
    println("Main continues")
    
    // 等待协程完成 (可选)
    job.join()
    
    println("Main ends")
    

    }
    “`

    输出:
    Main starts
    Main continues
    Coroutine started
    Coroutine finished
    Main ends

    注意 “Main continues” 在 “Coroutine started” 之后立即输出,这表明 launch 是非阻塞的。join() 确保 runBlocking 不会在 launch 协程完成之前结束。

  3. async:

    • 用于启动一个新的协程,执行一个任务,并且期望返回结果
    • 它返回一个 Deferred<T> 对象,它继承自 Job,并且有一个 await() 方法。调用 await() 会暂停当前协程,直到 async 启动的协程计算出结果并返回。
    • async 通常用于需要并行执行多个任务并等待它们全部完成以获取结果的场景。

    “`kotlin
    import kotlinx.coroutines.*

    fun main() = runBlocking {
    println(“Main starts”)

    // 启动两个并行任务
    val deferred1: Deferred<String> = async {
        println("Task 1 started")
        delay(1000)
        println("Task 1 finished")
        "Result 1"
    }
    
    val deferred2: Deferred<String> = async {
        println("Task 2 started")
        delay(500) // task2 比 task1 快
        println("Task 2 finished")
        "Result 2"
    }
    
    println("Main continues")
    
    // 等待并获取两个任务的结果
    val result1 = deferred1.await()
    val result2 = deferred2.await()
    
    println("Results: $result1, $result2")
    
    println("Main ends")
    

    }
    “`

    输出:
    Main starts
    Main continues
    Task 1 started
    Task 2 started
    Task 2 finished
    Task 1 finished
    Results: Result 1, Result 2
    Main ends

    注意 “Task 1 started” 和 “Task 2 started” 几乎同时输出,表明它们是并行启动的。await() 方法是非阻塞的:当调用 deferred1.await() 时,如果 deferred1 还没有结果,当前协程会暂停,直到结果可用。这使得我们可以像写同步代码一样获取并行任务的结果。

协程上下文 (CoroutineContext) 和调度器 (Dispatcher)

每个协程都有一个相关的 CoroutineContext,它是一组元素的集合,定义了协程的行为。CoroutineContext 主要包括以下元素:

  • Job 控制协程的生命周期(运行中、完成、取消等),并负责结构化并发中的父子关系。
  • CoroutineDispatcher 决定协程在哪个线程或线程池上执行。这是控制协程在不同线程之间切换的关键。
  • CoroutineName 协程的名称,用于调试。
  • CoroutineId 协程的唯一 ID。

CoroutineDispatcherCoroutineContext 中最重要的元素之一,它决定了协程将在哪个线程上运行。Kotlin 协程提供了几种标准的调度器:

  1. Dispatchers.Main

    • 专用于 UI 线程。例如在 Android 中,它对应于主线程。
    • 用于执行与 UI 交互的代码,例如更新视图。
    • 注意: Dispatchers.Main 依赖于平台特定的实现,需要在相应的环境中添加依赖(如 kotlinx-coroutines-android)。
  2. Dispatchers.IO

    • 适用于执行 I/O 密集型任务,如网络请求、文件读写、数据库操作。
    • 它使用一个共享的按需创建的线程池,线程数量会根据需要增加,但有一个上限。
    • 切换到这个调度器可以避免在执行阻塞 I/O 操作时阻塞其他协程。
  3. Dispatchers.Default

    • 适用于执行 CPU 密集型任务,如大量的计算、排序、JSON 解析等。
    • 它使用一个共享的线程池,其大小默认等于 CPU 的核心数。
    • 将 CPU 密集型任务限制在这个线程池可以避免它们阻塞 I/O 线程或主线程。
  4. Dispatchers.Unconfined

    • 一个特殊的调度器。协程在调用它的线程上启动,但在第一个暂停点之后,它会在恢复它的线程上恢复执行。
    • 不限制协程在任何特定线程或线程池中。
    • 通常不用于常规代码,适用于某些特殊的高级场景,或者当你确定不需要任何特定的线程限制时。它可能导致协程在不同的线程之间跳跃,这使得调试变得困难。

如何切换调度器?

你可以通过 withContext 函数在协程内部切换调度器。withContext 是一个暂停函数,它会切换到指定的调度器执行一个代码块,并在代码块执行完成后,切换回原来的调度器。

“`kotlin
import kotlinx.coroutines.*

// 假设这是一个在主线程(例如 UI 线程)启动的协程
suspend fun refreshData() {
println(“Refreshing data on ${Thread.currentThread().name}”) // 通常是 Main 线程

// 切换到 IO 调度器执行网络请求
val result = withContext(Dispatchers.IO) {
    println("Fetching data on ${Thread.currentThread().name}") // 切换到 IO 线程池
    delay(2000) // 模拟网络延迟
    "Network Data"
}

// withContext 块执行完毕后,自动切换回原来的调度器 (这里是 Main)
println("Data fetched: $result on ${Thread.currentThread().name}") // 回到 Main 线程

// 在 Main 线程更新 UI
updateUI(result)

}

fun updateUI(data: String) {
println(“Updating UI with $data on ${Thread.currentThread().name}”) // 在 Main 线程执行
}

// 模拟 Main 线程环境 (在实际应用如 Android 中无需手动创建)
// 在简单的命令行例子中,我们可以用 runBlocking 模拟 Main 线程环境来测试
fun main() = runBlocking(Dispatchers.Main) { // 假设 runBlocking 在一个模拟的 Main Dispatcher 上
println(“Main thread starts: ${Thread.currentThread().name}”)
refreshData()
println(“Main thread ends: ${Thread.currentThread().name}”)
}

// 注意: 要使 Dispatchers.Main 工作,你需要一个平台特定的 Main 调度器实现。
// 在没有特定环境(如 Android)的情况下,上面的 runBlocking(Dispatchers.Main) 会使用一个简单的 EventLoop 模拟。
“`

使用 withContext 使得代码非常清晰:哪个代码块在哪个调度器上运行一目了然,并且代码仍然保持了同步的顺序风格。这是协程最强大的特性之一。

结构化并发 (Structured Concurrency)

Kotlin 协程提倡使用结构化并发。这意味着协程是有层级结构的,通常在一个 CoroutineScope 内启动的协程会成为该 Scope 的“子协程”。这种父子关系带来了几个重要的好处:

  1. 生命周期管理: 当一个 CoroutineScope 被取消时,它的所有子协程也会被递归取消。这有助于防止协程泄漏。
  2. 错误传播: 子协程的失败会向上传播,导致父协程失败(可以通过 SupervisorJob 进行修改,但默认行为是传播失败)。
  3. 可读性: 协程的生命周期与其启动的 Scope 或父协程的生命周期绑定,使得代码的意图更清晰,更容易理解和维护。

CoroutineScope 是一个接口,它将协程的生命周期绑定到一个特定的作用域。常见的 CoroutineScope 包括:

  • GlobalScope:这是一个顶层 Scope,它启动的协程与整个应用程序的生命周期绑定。不建议在实际应用中使用 GlobalScope.launchGlobalScope.async,因为它容易导致协程泄漏和难以取消。
  • 由协程构建器(如 runBlocking, launch, async)提供的默认 Scope:这些构建器本身就是 CoroutineScope 的扩展函数,它们会在调用它们的 Scope 中启动新的协程。
  • 自定义 Scope:你可以通过 CoroutineScope(context) 创建自己的 Scope,并将其绑定到特定的组件生命周期(例如 Android 中的 Activity/Fragment)。

考虑以下例子,展示结构化并发的取消:

“`kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
println(“Main starts”)

val parentJob = launch { // 这个 launch 在 runBlocking 的 Scope 中启动,成为 runBlocking 的子协程
    println("Parent coroutine started")

    // 在 parentJob 的 Scope 中启动子协程
    val childJob1 = launch {
        println("Child 1 started")
        try {
            delay(2000) // 模拟长时间工作
            println("Child 1 finished") // 这行可能不会执行到
        } finally {
            // 在取消时执行清理工作
            withContext(NonCancellable) { // NonCancellable 保证这块代码不会被取消中断
                println("Child 1 is cancelling...")
                delay(100) // 模拟清理耗时
                println("Child 1 cancelled")
            }
        }
    }

    val childJob2 = launch {
        println("Child 2 started")
        try {
            delay(1000) // 比 Child 1 短
            println("Child 2 finished")
        } finally {
             withContext(NonCancellable) {
                println("Child 2 is cancelling...")
                delay(100)
                println("Child 2 cancelled")
            }
        }
    }

    // 等待所有子协程完成 (如果父协程不被取消的话)
    // joinAll(childJob1, childJob2) // 如果父协程不取消,可以用这个等待子协程
    println("Parent is waiting for children...")
}

delay(500) // 让协程有时间启动

println("Cancelling parent job...")
parentJob.cancel() // 取消父协程

parentJob.join() // 等待父协程(以及它的子协程)完成取消

println("Main ends")

}
“`

输出(可能略有差异,取决于执行时机):

Main starts
Parent coroutine started
Child 1 started
Child 2 started
Parent is waiting for children...
Cancelling parent job...
Child 2 is cancelling...
Child 1 is cancelling...
Child 2 cancelled
Child 1 cancelled
Main ends

正如你所见,当我们取消 parentJob 时,它的两个子协程 childJob1childJob2 也被自动取消了。这就是结构化并发带来的便利:你只需要管理父协程的生命周期,子协程的生命周期会与其父协程绑定。

协程取消 (Cancellation)

协程的取消是协作式的。这意味着一个运行中的协程如果不想被取消,它就不会被取消。大多数 Kotlin 协程提供的暂停函数(如 delay, withContext, 网络库的 suspend 函数等)都是可取消的。当协程被取消时,这些暂停函数会检查取消状态并抛出 CancellationException 来使协程停止执行。

如果你在协程中执行长时间的非暂停计算(例如一个巨大的循环),你需要定期检查协程的取消状态,否则它将不会响应取消请求。

你可以使用 isActive 属性或调用 yield() 或任何其他暂停函数来检查取消状态:

“`kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
val job = launch(Dispatchers.Default) { // 在 Default 调度器上启动计算密集型任务
var i = 0
while (isActive) { // 检查协程是否活跃 (未被取消)
// 执行计算密集型工作
i++
if (i % 100_000_000 == 0) {
println(“Processing $i…”)
}
// yield() // 或者定期调用 yield() 也可以检查并让出线程
}
println(“Coroutine finished processing (should not reach here if cancelled)”)
}

delay(100) // 给协程一点时间运行
println("Cancelling the computation...")
job.cancel() // 请求取消
job.join() // 等待协程完成取消

println("Main ends")

}
“`

输出:

Processing 100000000...
Cancelling the computation...
Main ends

job.cancel() 被调用后,在 while 循环的下一次迭代中,isActive 会变为 false,循环终止,协程结束执行。

finally 块中执行清理:

正如在结构化并发例子中看到的,你可以在 try/finally 块的 finally 部分执行资源清理工作。即使协程在 try 块中被取消,finally 块也会执行。如果清理工作本身也是暂停的,并且需要保证在取消时一定完成,可以使用 withContext(NonCancellable) 包装清理代码。

协程异常处理 (Exception Handling)

协程中的异常处理与普通代码类似,可以使用 try/catch。然而,在不同的协程构建器中,异常的处理方式略有不同,并且结构化并发会影响异常的传播。

  • launch 如果一个 launch 协程内部抛出未捕获的异常,它会被视为该协程的失败。这个失败会向上传播给父协程,导致父协程被取消(默认行为)。如果父协程没有处理这个异常(例如,没有设置 CoroutineExceptionHandler 或没有使用 SupervisorJob),它可能会一直传播到协程层级的根部,最终可能导致程序崩溃(取决于平台和顶层协程的设置)。
  • async async 协程内部抛出的异常会被延迟到你调用 await() 方法时才抛出。这意味着你需要在调用 await() 的地方使用 try/catch 来捕获异常。

“`kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
// 例子 1: launch 中的异常传播
val parentJob = launch { // runBlocking 的子协程
launch { // parentJob 的子协程
delay(100)
throw RuntimeException(“Exception in child launch!”) // 未被捕获的异常
}
// 这行可能不会执行到,因为子协程的异常会导致父协程被取消
delay(1000)
println(“Parent launch finished”)
}

try {
    parentJob.join() // 等待父协程完成,如果子协程导致父协程失败,join 会抛出异常
} catch (e: Exception) {
    println("Caught exception in main: ${e.message}")
}

println("---")

// 例子 2: async 中的异常延迟抛出
val deferred = async { // runBlocking 的子协程
    delay(100)
    throw RuntimeException("Exception in async!") // 异常在这里发生,但不会立即抛出
    "Async result" // 这行不会执行到
}

try {
    deferred.await() // 异常在这里被抛出
} catch (e: Exception) {
    println("Caught exception in main: ${e.message}")
}

println("Main ends")

}
“`

输出:

“`
Caught exception in main: Exception in child launch!


Caught exception in main: Exception in async!
Main ends
“`

SupervisorJob:

如果你希望子协程的失败不会影响到它的兄弟协程或父协程,你可以使用 SupervisorJobSupervisorJob 修改了异常传播的默认行为,子协程的失败不会向上传播,而是由子协程自身或其自定义的 CoroutineExceptionHandler 来处理。这在 Android UI 开发中非常有用,你可能希望某个 UI 组件内的协程失败不会导致整个组件或屏幕崩溃。

“`kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
val supervisor = SupervisorJob() // 创建一个 SupervisorJob

val scope = CoroutineScope(coroutineContext + supervisor) // 将 SupervisorJob 添加到 Scope 中

scope.launch { // 在带有 SupervisorJob 的 Scope 中启动
    launch { // 第一个子协程
        delay(100)
        println("Child 1 finished")
    }
    launch { // 第二个子协程
        delay(50)
        throw RuntimeException("Exception in child 2!") // 这个异常不会取消 sibling (child 1)
    }
}

delay(200) // 等待协程完成或失败
println("Main ends")

}
“`

输出:

Child 1 finished
Exception in thread "..." java.lang.RuntimeException: Exception in child 2! // 异常可能由默认的 UncaughtExceptionHandler 处理并打印
Main ends

注意,虽然异常没有取消 child 1,但它仍然是一个未捕获的异常。为了妥善处理它,你通常需要结合 CoroutineExceptionHandler

协程的优势总结

与传统方法相比,Kotlin 协程的优势显而易见:

  1. 轻量级和可伸缩性: 协程比线程轻量得多,可以轻松创建和运行成千上万个协程,适合高并发场景(如服务器端应用)。
  2. 易读性: 使用 suspend 函数和 withContext,你可以用顺序式的代码风格编写复杂的异步逻辑,避免了回调嵌套,提高了代码可读性和可维护性。
  3. 结构化并发: 内置的结构化并发机制使得协程的生命周期管理、取消和错误处理更加安全可靠,减少了协程泄漏的风险。
  4. 内置于 Kotlin 语言: 协程是 Kotlin 语言的一部分(通过库实现),与语言特性无缝集成,并且与现有的 Kotlin 库(如 Flow)以及 Java 库具有良好的互操作性。
  5. 灵活性: 方便地切换调度器 (withContext) 使你可以精确控制代码在哪个线程上执行,从而优化性能并避免阻塞。

如何开始使用 Kotlin 协程

要在你的 Kotlin 项目中使用协程,你需要添加相应的依赖。对于 JVM 项目或 Android 项目,通常需要 kotlinx-coroutines-core。如果是在 Android 中使用 Dispatchers.Main,还需要 kotlinx-coroutines-android

在 build.gradle (Kotlin DSL) 中:

kotlin
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") // 或最新版本
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") // 适用于 Android
// ... 其他依赖
}

在 build.gradle (Groovy) 中:

gradle
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3' // 或最新版本
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' // 适用于 Android
// ... 其他依赖
}

添加依赖后,你就可以开始在你的代码中使用协程构建器和暂停函数了。

总结

Kotlin 协程是 Kotlin 语言在异步编程领域提供的一个强大而优雅的解决方案。通过引入 suspend 函数和协程构建器,它让复杂的异步逻辑变得像同步代码一样直观易懂。结构化并发、调度器和取消机制进一步提高了协程的可靠性和易用性。

对于任何需要进行异步操作的 Kotlin 项目,无论是 Android 应用、服务器端应用还是其他 JVM 应用,协程都是一个值得深入学习和使用的强大工具。告别回调地狱的困扰,拥抱更简洁、更安全、更易于维护的异步编程新范式吧!

这仅仅是 Kotlin 协程的入门介绍。协程库还提供了更多高级特性,例如 Flow(用于处理异步数据流)、Channels(用于协程之间的通信)等。当你掌握了基础概念后,可以进一步探索这些高级主题。

希望本文能帮助你迈出 Kotlin 协程学习的第一步!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部