Kotlin-Koroutinen – Grundlegende Verwendung von Koroutinen

Grundlegende Verwendung von Kotlin-Coroutinen

Kotlin Coroutine-Serie:

  • Grundlegende Verwendung von Coroutinen (dieser Artikel)
  • Kontextuelles Verständnis von Coroutinen
  • Verwaltung des Coroutine-Umfangs
  • Gemeinsame fortgeschrittene Verwendung von Coroutinen

Tatsächlich gibt es Kotlin协程im , und hier werde ich eines veröffentlichen, um meine eigene Zusammenfassung aufzunehmen. Es ist auch unser eigenes Verständnis und Ergebnis.

Da das Konzept der Coroutine eine relativ große Sache ist, werde ich es hier in mehrere verschiedene Module aufteilen, um es zu erklären.Dieser Artikel ist die erste Ausgabe der Serie.In dieser Serie geht es um die praktische Verwendung jedes Moduls der Coroutine.Die meisten davon es handelt sich um das Prinzip und den Quellcode.Dieser Artikel spricht über die grundlegende Verwendung von Coroutinen.

Wie Sie sehen, habe ich den Coroutinen hier das Präfix vorangestellt Kotlin协程, und die Coroutinen, auf die später in der Serie verwiesen wird, sind alle Kotlin协程. Es ist bereits bekannt, dass Kotlin-Coroutinen anders implementiert sind als Coroutinen in anderen Sprachen. Ich werde hier nicht zu viel vorstellen, wenn Sie es nicht verstehen, sehen Sie hier .

1. Warum Coroutinen verwenden?

Die Android-Entwicklung verwendet die Java-Sprache und die Thread-Verwaltung von Java wird verwendet Thread. Wir haben es mehr oder weniger in der Android-Entwicklung verwendet Thread.

Aber wir müssen die UI im Haupt-Thread aktualisieren.Nachdem wir die Logik im Sub-Thread verarbeitet Threadhaben, müssen wir Api aufrufen runOnUiThread, um zum Haupt-Thread zu wechseln, um die UI zu aktualisieren, einschließlich der Kotlin-Syntax.Es ist dieselbe Routine, die verwendet werden muss. Obwohl es eine Thread-Erweiterungsmethode gibt, ist die interne Der Verarbeitungsablauf ist derselbe wie in Java

Der Pseudocode lautet wie folgt:

   thread {
        dosth()

        runOnUiThread {
            updateUI()
        }
    }

Wenn es zu viel Logik oder häufiges Wechseln von Threads gibt, wird sogar der syntaktische Zucker von Kotlin viele Male verschachtelt, sodass wir einige hervorragende Frameworks verwenden werden RxJava, um einige asynchrone und gleichzeitige Vorgänge zu verwalten und die Wechsel-Thread-Logik zu verwalten.

而得益于Kotlin语言的设计,在1.3版本加入了协程的概念,后期又出了一些Jetpack的组件,天然支持协程,使得协程的概念越来越为人熟知,更多的人开始使用协程了。而我们也就无需使用 RxJava 等第三方框架来管理线程了。

协程的特点:

  • 轻量:您可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
  • 更少的内存泄漏:使用结构化并发机制在一个作用域内执行多项操作
  • 支持取消:取消操作会自动在运行中的整个协程层次结构内传播。
  • Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发

二、怎么使用协程

Kotlin语法是默认不带协程的,如果我们想使用协程还是需要引入协程框架库。注意需要Kotlin版本1.3以上。这里我使用的Kotlin版本为1.4.21。

协程库的引入:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'

我们就能使用一个简单的协程:

       GlobalScope.launch {
            YYLogUtils.w("执行在协程中...")

            delay(1000L)

            YYLogUtils.w("执行完毕...")
        }

Kotlin中,有几种方式能够启动协程,如下所示:

  • launch{} CoroutineScope的扩展方法,启动一个协程,不阻塞当前协程,并返回新协程的Job。

  • async{} CoroutineScope的扩展方法,启动一个协程,不阻塞当前协程,返回一个Deffer,除包装了未来的结果外,其余特性与launch{}一致

  • runBlocking{} 是一个裸方法,创建一个协程,并阻塞当前线程,直到协程执行完毕。前面说过,这里不再赘述。

  • withContext(){} 一个suspend方法,在给定的上下文执行给定挂起块并返回结果,它并不启动协程,只会(可能会)导致线程的切换。用它执行的挂起块中的上下文是当前协程的上下文和由它执行的上下文的合并结果。 withContext的目的不在于启动子协程,它最初用于将长耗时操作从UI线程切走,完事再切回来。

  • coroutineScope{} 一个suspend方法,创建一个新的作用域,并在该作用域内执行指定代码块,它并不启动协程。其存在的目的是进行符合结构化并发的并行分解(即,将长耗时任务拆分为并发的多个短耗时任务,并等待所有并发任务完成后再返回)。

可以看到只有前三种方法是创建或启动一个协程的,后面那种方式都是切换线程,或者创建作用域的一个方法。

我们举例说明一下

    coroutineScope {  }  //报错 ,不能直接用,只能在协程里面使用
        runBlocking {
            coroutineScope {  }   //正常使用,因为runBlocking创建了协程
           
             YYLogUtils.w("执行在协程中...")

            delay(1000L)

            YYLogUtils.w("执行完毕...")
        }
    runBlocking {
            coroutineScope {  //这里包一层也没什么,只是多了一层代码块而已, 不影响逻辑

            YYLogUtils.w("执行在协程中...")

            delay(1000L)

            YYLogUtils.w("执行完毕...")
            }
        }

源码查看

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job

launch返回的是Job对象,用于控制协程的生命周期

异步的启动

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>

async返回的是Deferred用于等待未来结果的返回。一般使用 await 来调用获取结果。

public suspend fun await(): T

切换线程withContext

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T 

内部需要传入一个协程上下文,我们一般使用调度器Dispatchers来切换线程,它是协程上下文CoroutineContext的实现类之一。(后面会单独出一期)

  • Dispatchers.Main Android主线程
  • Dispatchers.Unconfined 当前CoroutineScope的线程策略
  • Dispatchers.Default 默认值,为JVM共享线程池
  • Dispatchers.IO IO线程池,默认为64个线程

通过lauch 和 async 和 withContext 的使用示例如下:

     GlobalScope.launch{
            YYLogUtils.w("执行在协程中...")

            delay(1000L)

            val deferred = async {
                YYLogUtils.w("切换到另一个协程")
                Thread.sleep(2000)
                return@async "response data"
            }

            val response = deferred.await()
            YYLogUtils.w("response:$response")


            val result = withContext(Dispatchers.IO) {
                //异步执行
                delay(1000L)
                YYLogUtils.w("切换到另一个协程")
                return@withContext "1234"
            }

            YYLogUtils.w("result:$result")

            delay(1000L)

            YYLogUtils.w("执行完毕...")

        }

这也是我们常用的两种方式:async是异步执行,withContext是同步执行。

当它们的代码块执行完毕,就会回到主协程的线程中,换句话说就是通过调度器实现切换线程,执行完就回到当前线程。如果想知道底层逻辑可以看这里

到这里就会简单的协程使用了,但是注意上面的代码有时候用的 sleep 有时候用的 delay ,看着意思都是延迟的意思,有什么区别?

三、协程的阻塞与挂起

阻塞的意思就是会阻断当前线程后面的代码不会执行。挂起的全名应该叫非阻塞式挂起,其意思是为不会阻塞其他协程,只是当前自己所在协程会挂起等待不执行,但是其他协程还是能继续执行的。

3.1 suspend非阻塞挂起函数

我们使用AS来编程,就很清晰的可以看到,左侧有箭头的就是挂起,而sleep方法是没有箭头的就不是挂起而是阻塞。

而挂起的方法调用都是需要 suspend 标记的,如

public suspend fun delay(timeMillis: Long) {
...
}

我们举一个简单的例子说明:

     GlobalScope.launch{
           YYLogUtils.w("执行在协程中...")

           val result1 = withContext(Dispatchers.IO) {
                //异步执行
                YYLogUtils.w("异步执行result1")
                delay(1000L)
                return@withContext "1234"
            }

            YYLogUtils.w("result1:$result1")

            val result2 = withContext(Dispatchers.IO) {
                //异步执行
                YYLogUtils.w("异步执行result2")
                delay(1000L)
                return@withContext "123456"
            }
            YYLogUtils.w("result2:$result2")

            YYLogUtils.w("执行完毕...")

        }

打印Log如下:

这是正常的,顺序执行的。为什么‘阻塞’了?不是说 delay 函数是挂起函数,是非阻塞的吗?OK,再次强调一点,此阻塞的概念并非是说阻塞这个线程,阻塞这段代码不让执行,此阻塞是针对其他 协程 的。上面的 withContext 它创建/启动了了协程吗?没有,它只是切换了线程,它本身其实也是 suspend 的函数而已。所以上面的代码是顺序执行的。

下面我们修改一下代码为启动协程:

     GlobalScope.launch{
           YYLogUtils.w("执行在协程中...")

           GlobalScope.launch(Dispatchers.IO) {
                //异步执行
                YYLogUtils.w("异步执行result1")
                delay(1000L)
                YYLogUtils.w("result1:1234")
            }

            GlobalScope.launch(Dispatchers.IO) {
                //异步执行
                YYLogUtils.w("异步执行result2")
                delay(1000L)
                YYLogUtils.w("result2:123456")
            }

            YYLogUtils.w("执行完毕...")

        }

挂起函数是不会阻塞协程的,打印Log如下:

而我们自定义的函数方法,也可以通过标记 suspend 而在协程中使用

GlobalScope.launch{
           YYLogUtils.w("执行在协程中...")

           saveSth2Local()

           val result1 = withContext(Dispatchers.IO) {
                //异步执行
                YYLogUtils.w("异步执行result1")
                delay(1000L)

                saveSth2Local()

                return@withContext "1234"
            }

            YYLogUtils.w("result1:$result1")

           

            YYLogUtils.w("执行完毕...")

        }

   suspend fun saveSth2Local() {
        DBHelper.get().saveUser()
    }       

使用我们定义的 saveSth2Local 挂起方法的时候,在哪个作用域使用就是在哪个线程执行,如上面的 saveSth2Local 方法是在主线程执行,withContext 中的 saveSth2Local 方法则是在子线程中使用。

3.2 runBlocking阻塞协程

上面我们讲到的是 suspend 挂起函数的阻塞与非阻塞的概念,而我们启动函数launch 和 runBlocking 也是区分阻塞与非阻塞的。概念都是一样的,就是是否阻塞其他协程。

launch的是非阻塞的,runBlocking就是阻塞的,它会阻止其他协程的运行。

Derselbe Code, ändern wir GlobalScope.launch in runBlocking und versuchen:

    GlobalScope.launch{
          YYLogUtils.w("执行在协程中...")

          runBlocking(Dispatchers.IO) {
               //异步执行
               YYLogUtils.w("异步执行result1")
               delay(1000L)
               YYLogUtils.w("result1:1234")
           }

           runBlocking(Dispatchers.IO) {
               //异步执行
               YYLogUtils.w("异步执行result2")
               delay(1000L)
               YYLogUtils.w("result2:123456")
           }

           YYLogUtils.w("执行完毕...")

       }

Ergebnis des Laufens:

Es ist ersichtlich, dass runBlocking wirklich verhindert, dass andere Coroutinen ausgeführt werden, und dass sie sich selbst ausführen müssen, bevor sie mit der Ausführung anderer Coroutinen fortfahren können. Das blockiert.

Unabhängig davon, ob es sich um einen Bereich handelt oder nicht, ob es sich um eine Geschwister-Coroutine auf derselben Ebene oder um eine Eltern-Kind-Coroutine handelt, wird sie blockiert.

        CoroutineScope(Dispatchers.Main).launch {
            YYLogUtils.w("执行在协程中...")

            withContext(Dispatchers.IO) {
                //异步执行
                YYLogUtils.w("异步执行result1")
                delay(1000L)
                YYLogUtils.w("result1:1234")
            }

            withContext(Dispatchers.IO) {
                //异步执行
                YYLogUtils.w("异步执行result2")
                delay(1000L)
                YYLogUtils.w("result2:123456")
            }

            delay(1000L)

            YYLogUtils.w("执行完毕...")

        }


        GlobalScope.launch(Dispatchers.Main) {
            YYLogUtils.w("执行在另一个协程中...")

            delay(1000L)

            YYLogUtils.w("另一个协程执行完毕...")
        }

Wie im obigen Code werden die Coroutinen der beiden übergeordneten Bereiche gleichzeitig ausgeführt. Sobald die erste Coroutine nicht blockiert ist, können die folgenden Coroutinen ausgeführt werden. Zu diesem Zeitpunkt sind die beiden Coroutinen gleichzeitig und das Protokoll ist wie folgt:

Aber wenn wir den Code ändern, um die obige Coroutine zu blockieren, muss die folgende Coroutine warten, bis der blockierte Code ausgeführt wird, bevor sie ausgeführt werden kann. Zu diesem Zeitpunkt werden die beiden Coroutinen in nicht gleichzeitiger serieller Reihenfolge ausgeführt.

        CoroutineScope(Dispatchers.Main).launch {
            YYLogUtils.w("执行在协程中...")

            runBlocking(Dispatchers.IO) {
                //异步执行
                YYLogUtils.w("异步执行result1")
                delay(1000L)
                YYLogUtils.w("result1:1234")
            }

            runBlocking(Dispatchers.IO) {
                //异步执行
                YYLogUtils.w("异步执行result2")
                delay(1000L)
                YYLogUtils.w("result2:123456")
            }

            delay(1000L)

            YYLogUtils.w("执行完毕...")

        }


        GlobalScope.launch(Dispatchers.Main) {
            YYLogUtils.w("执行在另一个协程中...")

            delay(1000L)

            YYLogUtils.w("另一个协程执行完毕...")
        }

Die nicht gleichzeitigen Druckergebnisse lauten wie folgt:

Diese Erklärung geht davon aus, dass jeder in der Lage sein sollte, das Konzept des Blockierens zu verstehen.

Zusammenfassen

Dieser Artikel ist der erste in einer Reihe über die Verwendung von Coroutinen. Grundlegende Verwendung. Dieser Artikel behandelt nur grundlegende Konzepte und die grundlegende Verwendung. Das oben erwähnte, dass Coroutinen einen Lebenszyklus haben, das Abbrechen unterstützen und andere Funktionen, die im nächsten behandelt werden wenige Probleme.

Was wir in dieser Ausgabe beherrschen müssen, sind verschiedene Möglichkeiten zum Starten von Coroutinen, mehrere Möglichkeiten zum Wechseln von Threads, die Ähnlichkeiten und Unterschiede zwischen asynchroner und synchroner Ausführung, Suspend-Funktionen und die Konzepte von Blockierung und Nicht-Blockierung.

Das Konzept und der Rahmen von Coroutinen sind relativ groß. Wenn ich Fehler oder Fehler in meiner Erklärung habe, hoffe ich, dass die Schüler darauf hinweisen und sie kommunizieren können.

Wenn Sie das Gefühl haben, dass dieser Artikel Sie ein wenig inspiriert hat, hoffe ich, dass Sie 点赞mich unterstützen können, denn Ihre Unterstützung ist meine größte Motivation.

Ok, das ist das Ende dieser Ausgabe.

Ich nehme an der Rekrutierung des Signierprogramms für Ersteller der Nuggets Technology Community teil. Klicken Sie auf den Link, um sich zu registrieren und einzureichen .

Ich denke du magst

Origin juejin.im/post/7117555477805793316
Empfohlen
Rangfolge