协程:打破线程结构的前言
与Java相比,协程是最大的优势。 需要了解协程的设计理念和知识体系,建立协程思想模型。 本文将介绍协程的概念、特点和原理,以及如何在开发中使用协程来简化并发编程、优化软件架构。
什么是协程
协程是一种互相协作的程序,可以随意暂停和恢复,每次都返回一个值,而不是像普通程序那样最后只返回一次。 协程可以看作是用户态的轻量级线程。 它们不依赖于操作系统的调度,而是由程序员控制它们的执行过程。
例如,假设我们有一个通用函数 (),它从 API 获取图像并向其添加雪花滤镜。 我们想在主线程中调用这个函数并显示处理后的图像。 我们可以这样写:
fun showSnowyBitmap() {
val snowyBitmap = getFilteredBitmap() // 堵塞主线程
showBitmap(snowyBitmap) // 显现图片
}
这样的代码有一个很大的问题:()是一个耗时的操作,会阻塞主线程,导致应用程序无响应。 为了解决这个问题,我们通常使用异步回调或者其他方法在后台线程中执行这个操作,并在主线程中接收结果。 但此类代码可能会变得复杂且难以保护。
如果我们把()改成协程,我们就可以写出更加简洁直观的异步代码。 我们可以这样写:
suspend fun showSnowyBitmap() = coroutineScope {
val snowyBitmap = async { getFilteredBitmap() } // 在后台线程中发动一个协程
showBitmap(snowyBitmap.await()) // 在主线程中等待成果并显现图片
}
这样的代码看起来像同步代码,但实际上它是错误阻塞的。 async 是一个协程构建器,它将在后台线程中启动一个新的协程并返回一个指示异步计算结果的对象。 Await 是一个暂停函数,它会暂停当前的协程,直到目标完成并返回结果。 挂起当前协程不会阻塞主线程,但会放弃其他协程或任务的执行权。 当目标完成时,当前协程将自动恢复执行并继续下面的代码。
协程
协程需求完全依赖协程库,协程结构是一个整体结构。 协程比线程更轻量、响应更灵敏、效率更高,并且可以在不同线程之间切换。
协程库
要使用协程,我们需要将以下依赖项添加到应用程序的 build.file 中:
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}
该库包含以下部分:
协程结构
协程结构是一个整体结构,包括以下几个层次:
层:不同的平台提供了不同的实现方式,比如JVM、JS等。通道层主要负责完成挂起函数的机制和调度器的逻辑。协程和线程
协程和线程都是并发编程方式,但它们有很多区别:
协程的思想模型
协程可以看作是在线程中运行的任务。 每个Task都有一个“句柄”或者“钩子”,方便我们暂停和恢复它。 协程不绑定到特定的线程,可以在不同的线程之间灵活切换。
我们可以用一个图来说明协程的思想模型:
+-----------------+ +-----------------+ +-----------------+
| Thread 1 | | Thread 2 | | Thread 3 |
+-----------------+ +-----------------+ +-----------------+
| | | | | |
| +---------+ | | +---------+ | | +---------+ |
| | Task A |--->|---->| | Task A |--->|---->| | Task A | |
| +---------+ | | +---------+ | | +---------+ |
| | | | | |
| +---------+ | | +---------+ | | +---------+ |
| | Task B |--->|---->| | Task B |--->|---->| | Task B |--->|
| +---------+ | | +---------+ | | +---------+ |
| | | | | |
| +---------+ | | +---------+ | | +---------+ |
| | Task C |--->|---->| | Task C |--->|---->| | Task C |--->|
| +---------+ | | +---------+ | | +---------+ |
| | | | | |
+-----------------+ +-----------------+ +-----------------+
在这张图中,我们有三个线程和三个协程(任务 A、B、C)。 每个协程可以运行在任何线程中,只要有一个“句柄”或“钩子”来控制它们的暂停和恢复。 这个“句柄”或“钩子”就是协程上下文,其中包含调度程序、作业、异常处理程序等信息。 我们可以通过协程构建器或范围来指定或修改协程上下文。
协程的非阻塞特性
由于协程具有挂起和恢复的能力,因此可以实现非阻塞的特性。 这意味着当协程遇到耗时操作时,它不会阻塞后续任务的执行,而是放弃其他任务的执行权,等待合适的机会恢复执行。
例如,假设我们有两个协程,任务 A 和任务 B。任务 A 需要从网络获取一些数据,任务 B 需要从数据库获取一些数据。 我们可以这样写:
suspend fun getData() = coroutineScope {
val dataFromNetwork = async { fetchFromNetwork() } // 在后台线程中发动一个协程,从网络获取数据
val dataFromDatabase = async { fetchFromDatabase() } // 在后台线程中发动一个协程,从数据库获取数据
val result = combine(dataFromNetwork.await(), dataFromDatabase.await()) // 在主线程中等待成果并组合
result // 回来成果
}
在此示例中,我们使用异步协程构建器来启动两个协程,即 和 。 它们都返回一个表示异步计算结果的对象。 我们使用await()悬挂函数来等待它们的结果并将它们组合成最终结果。
注意,当我们调用await()时,当前线程不会被阻塞,但当前协程会被挂起。 这样就可以在当前线程中执行其他协程或任务。 当目标完成时,当前协程将自动恢复执行并继续下面的代码。
这样的代码有以下优点:
总结
本文介绍了协程的概念、特点和原理,以及如何在开发中使用协程来简化并发编程、优化软件架构。 我们学习了以下常识点: