Kotlin协程:打破线程结构的思想

前语

协程是Kotlin比照Java的最大优势,需求理解协程的设计理念和常识体系,树立协程思想模型。本文将介绍协程的概念、特性和原理,以及如安在Android开发中运用协程来简化并发编程和优化软件架构。

什么是协程

协程是一种相互协作的程序,能够在恣意当地挂起和康复,每次回来一个值,而不是像一般程序那样只能在末尾回来一次。协程能够看成是一种用户态的轻量级线程,它不依靠于操作系统的调度,而是由程序员操控它们的履行流程。

举个比如,假设咱们有一个一般的函数getFilteredBitmap(),它从一个API获取一张图片,而且给它加上一个雪花滤镜。咱们想要在主线程中调用这个函数,而且显现处理后的图片。咱们能够这样写:

fun showSnowyBitmap() {
  val snowyBitmap = getFilteredBitmap() // 堵塞主线程
  showBitmap(snowyBitmap) // 显现图片
}

这样的代码有一个很大的问题:getFilteredBitmap()是一个耗时的操作,它会堵塞主线程,导致运用无响应。为了处理这个问题,咱们通常会运用异步回调或许RxJava等方法来在后台线程中履行这个操作,而且在主线程中接收成果。可是这样的代码会变得复杂和难以保护。

假如咱们把getFilteredBitmap()改成一个协程,咱们就能够用一种更简练和直观的方法来写异步代码。咱们能够这样写:

suspend fun showSnowyBitmap() = coroutineScope {
  val snowyBitmap = async { getFilteredBitmap() } // 在后台线程中发动一个协程
  showBitmap(snowyBitmap.await()) // 在主线程中等待成果并显现图片
}

这样的代码看起来就像是同步的代码,但实际上它对错堵塞的。async是一个协程构建器,它会在后台线程中发动一个新的协程,而且回来一个Deferred目标,表明异步核算的成果。await是一个挂起函数,它会挂起当时协程,直到Deferred目标完结,而且回来成果。挂起当时协程并不会堵塞主线程,而是让出履行权给其他协程或许使命。当Deferred目标完结时,当时协程会主动康复履行,而且继续后边的代码。

Kotlin协程

Kotlin协程需求独自依靠协程库,协程结构是一个全体的结构。协程比线程更轻量级、更灵敏、更高效,能够在不同线程间切换。

协程库

要运用Kotlin协程,咱们需求添加以下依靠到咱们的app的build.gradle文件:

dependencies {
  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}

这个库包含了以下几个部分:

  • kotlinx-coroutines-core: 包含了中心的API和数据结构,如suspendlaunchasyncawaitCoroutineScopeJobDeferred等。
  • kotlinx-coroutines-android: 包含了Android渠道特有的API和数据结构,如Main调度器、LifecycleScope等。
  • kotlinx-coroutines-test: 包含了测验协程的东西类,如TestCoroutineDispatcherrunBlockingTest等。

协程结构

Kotlin协程结构是一个全体的结构,它包含了以下几个层次:

  • 语言层:Kotlin语言供给了suspend关键字,用于标记挂起函数。挂起函数是一种特别的函数,它能够在不堵塞线程的情况下暂停和康复履行。挂起函数只能在协程或许其他挂起函数中调用。
  • 库层:协程库供给了各种API和数据结构,用于创立和办理协程。主要有以下几个概念:
    • 协程构建器:用于发动新的协程,如launchasyncrunBlocking等。不同的协程构建器有不同的特色和用处,咱们会在后边具体介绍。
    • 协程作用域:用于界说协程的生命周期规模,如coroutineScopesupervisorScopeGlobalScope等。协程作用域能够嵌套运用,形成一个结构化的并发形式。咱们会在后边具体介绍。
    • 协程上下文:用于存储协程的相关信息,如调度器、Job、反常处理器等。协程上下文能够经过协程构建器或许作用域来指定或许修正。咱们会在后边具体介绍。
    • 调度器:用于指定协程运转的线程或许线程池,如Dispatchers.MainDispatchers.IODispatchers.Default等。调度器是协程上下文的一部分,咱们会在后边具体介绍。
    • Job:用于表明一个协程的使命状况,如是否激活、是否完结、是否撤销等。Job也是协程上下文的一部分,咱们会在后边具体介绍。
    • Deferred:用于表明一个异步核算的成果,它是一种特别的Job,能够经过await()来获取成果。Deferred通常由async()来创立,咱们会在后边具体介绍。
  • 渠道层:不同的渠道供给了不同的完结方法,如JVM、Android、JS、Native等。渠道层主要负责完结挂起函数的机制和调度器的逻辑。

协程和线程

协程和线程都是一种并发编程的方法,可是它们有很多不同点:

  • 线程是操作系统等级的概念,它依靠于操作系统的调度和办理。线程之间的切换需求保存和康复寄存器和栈信息,耗费较大。线程之间的通讯需求运用同享内存或许音讯队列等方法,容易呈现竞态条件或许死锁等问题。
  • 协程是用户态等级的概念,它依靠于程序员操控它们的履行流程。协程之间的切换只需求保存和康复少量信息,耗费较小。协程之间能够运用通道或许同享流等方法进行通讯,愈加安全和高效。

协程的思想模型

协程能够看成是运转在线程中的Task,每个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  |--->|
|  +---------+    |     |  +---------+    |     |  +---------+    |
|                 |     |                 |     |                 |
+-----------------+     +-----------------+     +-----------------+

在这个图中,咱们有三个线程和三个协程(Task A、B、C)。每个协程都能够在恣意的线程中运转,只需有一个”抓手”或许”挂钩”来操控它们的挂起和康复。这个”抓手”或许”挂钩”就是协程上下文,它包含了调度器、Job、反常处理器等信息。咱们能够经过协程构建器或许作用域来指定或许修正协程上下文。

协程的非堵塞特性

由于协程具有挂起和康复的才能,它就能够完结非堵塞的特性。这意味着当一个协程遇到耗时的操作时,它不会堵塞后边的使命履行,而是让出履行权给其他使命,比及适宜的时机再康复履行。

举个比如,假设咱们有两个协程,分别是Task A和Task B。Task A需求从网络获取一些数据,Task B需求从数据库获取一些数据。咱们能够这样写:

suspend fun getData() = coroutineScope {
  val dataFromNetwork = async { fetchFromNetwork() } // 在后台线程中发动一个协程,从网络获取数据
  val dataFromDatabase = async { fetchFromDatabase() } // 在后台线程中发动一个协程,从数据库获取数据
  val result = combine(dataFromNetwork.await(), dataFromDatabase.await()) // 在主线程中等待成果并组合
  result // 回来成果
}

在这个比如中,咱们运用了async协程构建器来发动两个协程,分别是dataFromNetworkdataFromDatabase。它们都回来一个Deferred目标,表明异步核算的成果。咱们运用了await()挂起函数来等待它们的成果,而且组合成一个终究的成果。

留意,当咱们调用await()时,并不会堵塞当时线程,而是挂起当时协程。这样,其他协程或许使命就能够在当时线程中履行。当Deferred目标完结时,当时协程会主动康复履行,而且继续后边的代码。

这样的代码有以下几个优点:

  • 它对错堵塞的,不会影响主线程的响应性。
  • 它是并发的,能够一起履行多个耗时的操作。
  • 它是顺序的,能够按照咱们期望的顺序写代码,而不需求运用回调或许嵌套等方法。
  • 它是结构化的,能够运用协程作用域来办理协程的生命周期和反常处理。

总结

本文介绍了Kotlin协程的概念、特性和原理,以及如安在Android开发中运用协程来简化并发编程和优化软件架构。咱们学习了以下几个常识点:

  • 协程是一种相互协作的程序,能够在恣意当地挂起和康复,每次回来一个值。

  • Kotlin协程需求独自依靠协程库,协程结构是一个全体的结构。

  • 协程比线程更轻量级、更灵敏、更高效,能够在不同线程间切换。

  • 协程能够看成是运转在线程中的Task,每个Task都有一个”抓手”或许”挂钩”,能够便利咱们对它进行挂起和康复。

  • 协程具有非堵塞的特性,能够让出履行权给其他使命,比及适宜的时机再康复履行。