一次朋友圈发布流程,理解 Kotlin 协程为什么重新定义异步代码组织方式

1 阅读4分钟

很多 Android 开发者刚接触 Kotlin 协程时,会有这样的认知:

协程是不是就是一个更轻量的线程?

于是很多文章会强调:

  • 协程比线程轻量;
  • 一个线程可以运行很多协程;
  • 协程切换成本低。 之前写过一篇

Kotlin 官网为什么不再强调“协程是轻量级线程”了?

如果你没看过,可以先看下。

下面通过一个朋友圈发布场景来看:


一个典型业务:发布朋友圈

假设用户选择多张图片:

流程:

选择图片


并发压缩图片


并发上传服务器


提交朋友圈数据


发布成功

这是一个非常典型的异步任务链。

如果使用传统线程池:

你需要自己管理:

  • 任务提交;
  • 任务完成计数;
  • 阶段切换;
  • 线程安全;
  • 生命周期;
  • 异常处理。

如果使用协程:

业务流程可以直接表达出来。


一、传统 ThreadPool

压缩10张图片

↓

全部完成

↓

上传10张图片

↓

全部完成

↓

提交朋友圈

问题就出现了。

[UI 线程] postMoment(uris)
   │
   ├─> Executor.execute (分发 9 个压缩任务)
   │     │
   │     ├─ [线程 A] 压缩图片1 -> synchronized { 存入 List } -> count++ -> count==9? (No)
   │     ├─ [线程 B] 压缩图片2 -> synchronized { 存入 List } -> count++ -> count==9? (No)
   │     └─ [线程 N] 压缩图片9 -> synchronized { 存入 List } -> count++ -> count==9? (Yes! 触发回调)
   │                                                                      │
   │     ┌────────────────────────────────── onCompressionFinished() <────┘
   │     │
   │     ├─> Executor.execute (分发 N 个上传任务)
   │     │    ├─ [线程 X] 上传图片1 -> synchronized { 存入 URL } -> count++ -> count==all? (No)
   │     │    └─ [线程 Z] 上传图片N -> synchronized { 存入 URL } -> count++ -> count==all? (Yes! 触发)
   │     │                                                                      │
   │     └─> ┌────────────────────────────── onUploadFinished() <───────────────┘
   │         │
   │         └─> Executor.execute { repository.finalizePostSync() }
   │
[UI 线程] 更新成功 UI 状态 (需通过 Handler 或 Flow.update)


代码的表达不是线性的,而是撕碎的

1. 需要自己记录任务完成数量

例如:

val count = AtomicInteger(0)
val current = count.incrementAndGet()
if(current == total){
    startUpload()
}

因为线程不知道:

什么时候所有任务完成。

所以开发者必须JUC 同步(这里可以不用AtomicInteger,但是JUC 同步肯定少不了)。


2. 需要处理共享数据

例如:

多个线程同时添加压缩结果:

val images = mutableListOf<ByteArray>()

必须:

synchronized(images){
    images.add(data)
}

否则:

可能发生:

线程1 add

线程2 add

数据竞争

3. 生命周期管理复杂

Android 最大的问题:

页面退出了。

但是线程还在:

Activity destroy


Thread继续上传


内存泄漏

所以需要:

executor.shutdown()

甚至:

shutdownNow()

再配合:

interrupt检查

二、Coroutine:把异步重新写回同步代码

协程版本:

[UI 线程] postMoment(uris)
   │
   ├─> launch (开启协程)
   │     │
   │     ├─> performCompression (并发压缩) ──┐
   │     │    ├─ async (图片1) -> 挂起等待     │
   │     │    ├─ async (图片2) -> 挂起等待     ├─> Dispatchers.Default (并发执行)
   │     │    └─ awaitAll() <─── 全部完成回调 ──┘
   │     │
   │     ├─> performUpload (并发上传) ──────┐
   │     │    ├─ async (图片1) -> 挂起等待     │
   │     │    ├─ async (图片2) -> 挂起等待     ├─> Dispatchers.IO (并发执行)
   │     │    └─ awaitAll() <─── 全部完成汇总 ──┘
   │     │
   │     └─> finalizePost (最终发布) ───────> Dispatchers.IO (单次执行)
   │
[UI 线程] 更新成功 UI 状态

代码阅读方式:

压缩

↓

上传

↓

提交

和业务流程完全一致。

但是内部:

依然是异步执行。


三、async + awaitAll:替代计数器等待

以前:

等待10张图片:

需要:

AtomicInteger
+
if(count==total)

现在:

val result =
images.map {
    async {
        compress(it)
    }

}.awaitAll()

含义:

创建多个任务:

Task1

Task2

Task3

Task4

然后:

awaitAll()

等待全部完成

开发者不需要知道:

哪个线程完成。

也不需要维护:

完成数量。


四、Structured Concurrency:协程最大的杀手锏

协程最重要的概念:

不是轻量。

而是:

结构化并发。

什么叫结构化并发?

任务拥有父子关系。

例如:

ViewModel

   |
   launch

      |
      +--压缩任务1

      +--压缩任务2

      +--上传任务1

      +--上传任务2

当 ViewModel 销毁:

viewModelScope.cancel()

结果:

父任务取消

↓

所有子任务取消

不会留下后台任务。


传统线程:

ViewModel

    |
 ThreadPool

    |
 Thread

线程之间没有天然关系。

生命周期:

需要人工维护。

五、协程并没有让任务执行更快

这里容易产生误解。

比如:

压缩图片:

800ms

上传:

1200ms

协程不会:

800ms → 400ms

因为:

真正耗时的是:

  • CPU计算;
  • 网络IO。

协程提升的是:

开发效率和系统可控性。

它减少的是:

AtomicInteger

synchronized

Callback

Future

CountDownLatch

线程生命周期管理


六、协程真正替代的是什么?

很多人说:

协程替代线程。

其实不准确。

线程依然存在。

协程真正替代的是:

过去为了组织异步流程而产生的大量复杂机制:

Callback

↓

Future

↓

CountDownLatch

↓

AtomicInteger

↓

synchronized

↓

状态机代码

最后

线程池解决的问题:

任务在哪里执行?

例如:

哪个线程执行上传?

协程解决的问题:

多个异步任务如何组合?

例如:

压缩完成后上传

上传完成后发布

页面退出自动取消

所以:

线程是执行资源,协程是异步流程的组织模型。

Kotlin 协程真正改变 Android 开发的地方,不是让线程消失,而是让开发者不用再JUC 同步状态。

以前:

Callback
+
线程池
++
计数器
+
生命周期管理

现在:

suspend
+
async
+
await
+
Structured Concurrency

这才是协程带来的架构级变化。

Screen_recording_20260802_081112.gif 源码: 一次朋友圈发布流程,理解 Kotlin 协程为什么重新定义异步代码组织方式