Kotlin Coroutines指南–Kotlin的并发编程

更新10/29/2018

介绍和动机

在本文中,您将了解Kotlin Coroutines:它们是什么,它们是什么样的,以及他们如何工作。使用Kotlin 1.3.0测试了所示的代码示例 Kotlinx.Coroutines. 1.0.0.
kotlin coroutines是“更大的功能”之一,如下引用所示,从JetBrains中取出 博客:

我们都知道阻止在高负荷下坏了,轮询是一个禁止的,世界变得越来越掌握和异步。许多语言(从2012年开始)通过诸如异步/ await关键字等专用语言构造的异步编程支持异步编程。在Kotlin中,我们概括了这个概念,使图书馆可以定义它们的此类构造的版本,而异步不是关键字,而只是一个函数。
这种设计允许整合不同的异步API:期货/承诺,回调传递等。它也足够一般,可以表达懒惰的生成器(产量)并涵盖一些其他用例。

KOTLIN团队介绍了科因稿,为并发编程提供了简单的手段。可能是我们广大大多数人已经使用了一些基于线程的并发工具,例如Java的并发API。我已经使用了并发Java代码,很多,大多数都同意API的成熟。

java. Concurrency vs. Kotlin Coroutines

如果你仍然抓住自己挣扎着 线程并发 在Java中,我可以推荐这本书 实践中的Java并发 to you.

虽然Java的并发工具是精心设计的,但通常很难利用和相当繁琐的使用。另一个问题是Java不会直接鼓励非阻塞编程。您经常发现自己开始螺纹,不记得他们昂贵并快速引入阻塞计算(由于锁,睡眠,等待等)。申请 非阻塞模式 或者,真的很难和容易出错。

另一方面,Kotlin Coroutines旨在更容易,并且通过隐藏来自开发人员的大部分复杂的东西,看起来像顺序代码。他们提供了一种运行异步代码的方法,而无需 堵塞 线程,为应用程序提供了新的可能性。计算而不是阻止线程,而不是阻止线程 暂停.
许多来源描述了金属卷曲,如“轻量级线程”;他们不是一个线程,因为我们知道它们,例如java。与线程相比,科策索主要是非常 便宜的 在他们的创作中,并且自然地带的开销是没有周围的。一个原因是它们不会直接映射到本机线程。如您所见,Coroutines在主要由库管理的线程池中执行。
另一个临界差异是“局限性”:线程非常有限,因为他们依靠可用的本机线程,另一边的科素差别几乎是免费的,并且可以立即启动数千个(尽管这取决于它们计算的内容)。

并发编程风格

例如,以各种语言存在不同类型的异步/并发编程样式,例如:
*基于呼叫(JavaScript)
*基于未来/承诺(Java,JavaScript)
*异步/等待基于(C#)和更多

所有这些概念都可以通过Coroutines实现,因为Kotlin不会直接指示任何这些。
作为一种额外的好处,而不是基于回调的编程,Coroutines促进了一种顺序类型的异步编程:虽然您的考程可以执行多个并行计算,但您的代码仍然可以顺序查看,就像我们喜欢它一样。

Kotlin Coroutines的概念

这个词和概念 “coroutine”除了新的东西。根据维基百科文章,它已于1958年创建。许多现代编程语言提供本土支持:C#,Go,Python,Ruby等。同样在Kotlin的过程中的实施通常基于所谓的 “延续”,这是“计算机程序控制状态的抽象表示”。我们将在本文的后一章中了解这一点。

使用科诉讼入门

要使用Kotlin Coroutines设置项目,请逐步使用此步骤 参考 或者只是查看我的示例存储库 GitHub. 并将其用作模板。

Kotlin Coroutines成分

As already hinted, the Kotlin coroutine library provides an understandable high-level API that lets us start quickly. One new modifier we need to learn is suspend, which is used to mark a method as "暂停".
我们将看看使用API​​的一些简单的例子 Kotlinx.Coroutines. 在下一节。但首先,让我们学会什么 暂停 function is.

暂停功能

Coroutines rely on the suspend keyword, which is a modifier used to mark functions as "暂停", i.e., that calls to such functions 可能会暂停 在任何时候。我们只能将这些函数从科图表或其他暂停函数调用。

suspend fun myMethod(p: String): Boolean {
    //...
}

正如我们在上面的示例中所看到的,暂停函数看起来像一个常规功能,其中添加了附加修改器。请记住,从正常功能中调用此类功能将导致 汇编错误.

我们可以将Coroutine视为常规以及暂停函数的呼叫序列。该序列可选地在其执行结束时提供结果。

动手

让我们终于看到一些具体的军衔行动。在第一个示例中,将显示基础知识:

fun main(args: Array<String>) = runBlocking { //(1)
    val job = GlobalScope.launch { //(2)
        val result = suspendingFunction() //(3)
        print("$result")
    }
    print("The result: ")
    job.join() //(4)
}
// prints "The result: 5"

在此示例中,您可以看到两个新功能, (1) runBlocking(2) launch,这两者都是例子 科素建设者. We can utilize various builders, and they all start a coroutine with different purposes: launch (fire and forget, can also be canceled), async (returns promise), runBlocking (blocks thread) and more. We can start coroutines in different scopes. In this example, the GlobalScope 是 used to spawn a launch coroutine that is as a result of this limited to the application lifecycle itself. This approach is fine for most examples we will see in this article, but it will most probably not be sufficient for your real-life applications. Following the concept of “结构化并发”,我们需要将科修表限制在不同的范围内,以使它们保持可维护和可管理。阅读关于概念的 CoroutineScope 这里.

让我们遵守这段代码的作用:内部科源启动 (2) launch 实际的工作是:我们称之为 (3) 暂停 function and then the coroutine prints its result. The main thread, after starting the coroutine, prints a String before the coroutine finishes.
Coroutines started by launch return a Job immediately, which we can use to cancel the computation or await its completion with (4) join() as we see here. Since calling join() may suspend, we need to wrap this call into another coroutine, which is why we use runBlocking as a wrapper. This concrete coroutine builder (1) "是 designed to bridge regular blocking code to suspending functions, and we can use it in main functions and tests“(引用 API.)。如果我们删除了作业的加入,程序将在Coroutine打印结果之前停止。

It's possible to spawn launch in the scope of the outer runBlocking coroutine directly. To do so, we change GlobalScope.launch to just launch. As a result, we can now also remove the explicit join since runBlocking won't complete before all of its child coroutines finish. This example again is a demonstration of 结构化并发,我们想在接下来更详细地观察我们的原则。

结构化并发

As mentioned in the previous section, we can structure our coroutines in a way that they are more manageable by creating a certain hierarchy between them. Imagine you work with a user interface which, for any reason, we need to terminate at a certain point due to some event. If we started coroutines which handle certain tasks in that UI, these also should be terminated when the main task stops. With coroutines, it's vital to keep in mind that each coroutine can run in a different scope. We often want to group multiple coroutines by a shared scope so that they can, for instance, be canceled altogether easily. Let's re-use the basic coroutine composition we saw in the first code snippet above but now with launch running in the scope of runBlocking 和 slightly different tasks:

fun main(args: Array<String>) {
    runBlocking {
        launch {
            delay(500)
            println("你好,从发布开始")
        }
        println("从推出后从Runbocking开始")
    }
    println("完成的runblocking.")
}

此代码打印以下输出:

从推出后从Runbocking开始
Hello from launch
finished runBlocking

This output tells us that runBlocking does not complete before its child coroutine started by launch finishes its work. Further, we can use this structure to easily delegate the cancellation on a certain coroutine down to its child coroutines:


fun main(args: Array<String>) { runBlocking { val outerLaunch = launch { launch { while (true) { delay(300) println("从第一个内部发射你好") } } launch { while (true) { delay(300) println("你好,第二内部发射") } } } println("外部发射后的旋转块你好") delay(800) outerLaunch.cancel() } println("完成的runblocking.") }

此代码打印如下所示:

外部发射后的旋转块你好
从第一个内部发射你好
你好,第二内部发射
从第一个内部发射你好
你好,第二内部发射
finished runBlocking

In this example, we can see a launch that creates an outer coroutine that then again launches two inner coroutines, all in the same scope. We cancel the outer coroutine which then delegates its cancelation to the inner coroutines, and nothing keeps running afterward. This approach also handles errors correctly since an exception happening in an arbitrary child coroutine will make all coroutines in its scope stop.

自定义范围

Last but not least, we want to go a step further and create our very own CoroutineScope. In the last examples, we simply used the scope given by runBlocking, which we only did for convenience. In real-life applications, it's necessary to create custom scopes to manage one's coroutines effectively. The API comes with a simple builder for this: coroutineScope. It's documentation states:

创建新的[CoroOtineCope]并调用此范围的指定暂停块。提供的范围从外部范围内继承它的[coroutinecope.coroutinecontext],但覆盖上下文的[作业]。此功能专为a而设计 并行分解 工作的。 当此范围内的任何子关系失败时,此范围会失败,所有子项都会被取消 (对于不同的行为,请参阅[SupervisorScope])。

fun main(args: Array<String>) = runBlocking {
    coroutineScope {
        val outerLaunch = launch {
            launch {
                while (true) {
                    delay(300)
                    println("从第一个内部发射你好")
                }
            }
            launch {
                while (true) {
                    delay(300)
                    println("你好,第二内部发射")
                }
            }
        }

        println("外部发射后的旋转块你好")
        delay(800)
        outerLaunch.cancel()
    }
    println("finished coroutineScope")
}

该代码看起来与我们之前看到的相似,但现在我们在自定义范围内运行我们的考文表。如需进一步参考,请阅读 这篇文章与科因表的结构化并发.

走强

更生动的例子如下:想象一下,您必须从您的应用程序发送电子邮件。请求收件人地址和呈现消息正文是两个昂贵​​的任务,虽然是彼此独立的。智能和使用Kotlin,您希望利用考程,并行执行两个任务。
This is shown here:

suspend fun sendEmail(r: String, msg: String): Boolean { //(6)
    delay(2000)
    println("Sent '$msg' to $r")
    return true
}

suspend fun getReceiverAddressFromDatabase(): String { //(4)
    delay(1000)
    return "coroutine@kotlin.org"
}

suspend fun sendEmailSuspending(): Boolean {
    val msg = GlobalScope.async { //(3)
        delay(500)
        "The message content"
    }
    val recipient = GlobalScope.async { 
        getReceiverAddressFromDatabase() //(5)
    } 
    println("Waiting for email data")
    val sendStatus = GlobalScope.async {
        sendEmail(recipient.await(), msg.await()) //(7)
    }
    return sendStatus.await() //(8)
}

fun main(args: Array<String>) = runBlocking { //(1)
    val job = GlobalScope.launch {
        sendEmailSuspending() //(2)
        println("Email sent successfully.")
    }
    job.join() //(9)
    println("Finished")
}

首先,如前一个例子所见,我们使用一个 (1) launch builder inside a runBlocking builder so that we can (9) 等待Coroutine的完成。这种结构不是新的,也不是 (2) calling a suspending function (sendEmailSuspending)。
此方法使用 (3) 内心科稿以获取消息内容和 (4) another suspend method getReceiverAddressFromDatabase for getting the address. We execute both tasks in a separate coroutine built with (5) async. Note, that the calls to delay represent a non-blocking, coroutine-suspending, alternative to Thread.sleep, which we use for mocking expensive computations here.

Async Coroutine Builder

The async builder is simple and easy in its conception. As we know from many other languages, it returns a 诺言, which is of type Deferred in Kotlin. By the way, promise, future, deferred or delay are often used interchangeably for describing the same concept: The async method 承诺 计算我们可以随时等待或请求的值。

We can observe the "等待ing" on Kotlin’s Deferred object in (7) 其中暂停功能 (6) 是 called with the results of both prior computations. The method await() 是 called on instances of Deferred which suspends until the results become available. The call to sendEmail 是 also part of an async builder. Finally, we await its completion in (8) 在返回结果之前。

共享变形状态

在阅读文章时,您已经有可能担心Coroutines之间的同步,因为我没有提到任何关于它的文章。至少,至少,这种担忧,因为科图所可以同时对共享状态进行工作。意识到这一点非常明显,就像我们从像Java这样的其他语言所知的那么重要。我们可以利用熟悉的策略 线程安全数据结构, 限制 执行到A. 单螺纹 或使用 .
除了共同的模式外,Kotlin Coroutines还鼓励“通过沟通分享”的概念(见 QA.)。

具体地,“演员”可用于代表我们在科因表之间安全地分享的状态。 Coroutines可以使用演员通过它们发送和接收消息。让我们看看这是如何作用的:

演员

sealed class CounterMsg {
    object IncCounter : CounterMsg() // one-way message to increment counter
    class GetCounter(val response: SendChannel<Int>) : CounterMsg() // a request with channel for reply.
}

fun counterActor() = GlobalScope.actor<CounterMsg> { //(1)
    var counter = 0 //(9) </b>actor state, not shared
    for (msg in channel) { // handle incoming messages
        when (msg) {
            is CounterMsg.IncCounter -> counter++ //(4)
            is CounterMsg.GetCounter -> msg.response.send(counter) //(3)
        }
    }
}

suspend fun getCurrentCount(counter: SendChannel<CounterMsg>): Int { //(8)
    val response = Channel<Int>() //(2)
    counter.send(CounterMsg.GetCounter(response))
    val receive = response.receive()
    println("Counter = $receive")
    return receive
}

fun main(args: Array<String>) = runBlocking<Unit> {
    val counter = counterActor()

    GlobalScope.launch { //(5)
            while(getCurrentCount(counter) < 100){
                delay(100)
                println("sending IncCounter message")
                counter.send(CounterMsg.IncCounter) //(7)
            }
        }

    GlobalScope.launch { //(6)
        while ( getCurrentCount(counter) < 100) {
            delay(200)
        }
    }.join()
    counter.close() // shutdown the actor
}

此示例显示了使用的用法 (1) Actor,这是一个在任何背景上工作的科素本身。演员抱着 (9) 相关的 状态 of this sample application, which is the mutable counter variable. Another important feature we haven’t considered so far is a (2) Channel.

渠道

渠道 提供一种转移一个方法 价值流, similar to what we know as BlockingQueue (enables producer-consumer pattern) in Java but without any blocking methods. Instead, sendreceive are suspending functions used for 提供和消耗 来自频道的对象,用a实现 FIFO strategy.

默认情况下,演员连接到此类Coroutines的此类渠道 (7) can use to communicate with other coroutines. In the example, the actor iterates over the stream of messages from its channel (for works with suspending calls) handling them according to their type: (4) IncCounter 消息使actor通过递增柜台来改变其状态 (3) GetCounter 使演员通过发送返回计数器状态 独立留言 to the GetCounter's SendChannel.
第一个科稿 (5) in main launches a task that sends (7) IncCounter 只要计数器小于100,就向演员到actor的消息。第二 (6) 一个等待直到计数器到达100.这两个科素都使用暂停功能 (8) getCurrentCounter, which sends a GetCounter message to the actor and suspends by waiting for receive to return.

正如我们所能看到的,整个可变的 状态限制 到特定的演员科素,解决了问题 共享变形状态 并遵循的原则 通过沟通分享.

更多功能和示例

您可以找到更多的示例和伟大的文档 这些 documents.

它如何运作 - kotlin coroutines的实施

Coroutines do not rely on features of the operating system or the JVM. Instead, the compiler transforms coroutines and suspend functions and produces a 国家机器 这能够一般地处理悬浮液,并通过暂停金冠保持状态。这个概念 启用此功能。作为编译器的每个暂停函数将持续添加到每个暂停函数。这种技术被称为 “延续传递风格”.

让我们看看以下功能在添加延续后如何看出:

suspend fun sampleSuspendFun(x: Int): Int {
    delay(2000)
    return x * x
}

编译器将此功能修改为类似的内容:

public static final Object sampleSuspendFun(int x, @NotNull Continuation var1)

It adds another parameter, the Continuation, to it. Now, if we call this function from a coroutine, the compiler will pass the code that occurs after the sampleSuspendFun function call as a continuation. After the sampleSuspendFun completes its work, it will trigger the continuation. That's just what we know from a callback-based programming model already but hidden by the compiler. To be honest, this is just a simplified explanation. You can learn more about the details 这里.

QA.与罗马伊丽皮罗夫(Jetbrains)

我有机会为罗马伊利米罗夫制定几个问题,他们为Jetbrains工作,谁对Kotlin Coroutines非常负责。阅读他所说的话:

问: 我什么时候应该使用科图表,并且有没有任何用例仍需要明确的线程?

A: 经验法则 经过 Roman:
科素是用于异步任务的 等待 对于大多数时间的事情。线程用于CPU密集型任务。

问: 这句话“轻量级线程”听起来不恰当,因为它掩盖了科纳丁的事实 依靠线程 那个图书馆在一个线程中执行它们。*我更喜欢更简单的东西,例如正在执行的,停止等“任务”等。为什么你决定像线程一样决定描述它们?

A: 罗马回答说,“轻量级线程”短语相当肤浅,“科学素是从用户的角度来看的许多方式。”

问: 如果Coroutines类似于线程,则必须有必要在不同的Coroutines之间同步共享状态,右图? *

A: 罗马告诉我,我们仍然可以使用已知的同步模式,但是当我们使用Coroutines时,强烈建议在没有任何可变的共享状态。相反,科纳丁鼓励“通过沟通分享”风格。

结论

与Java相反,Kotlin鼓励一个完全不同的并发编程风格,这是 非阻塞 并且自然不会让我们开始大量的本机线程。
编写并发Java代码通常暗示启动太多线程或忘记了对正确的线程池管理。虽然引入了这种巨大的开销,导致执行减慢的执行。作为一种替代方案,据说金素素是 “轻量级线程”,这描述了它们未映射到本机线程的事实,因此不要拖延我们通常必须处理的所有风险和问题(死锁,饥饿,例如,饥饿,例如,。正如我们所看到的那样,与金冠,我们通常不必担心阻止线程。此外,只要我们追求的,同步更直接,理想地没有必要 “通过沟通分享” principle.

Coroutines还使我们能够使用几种不同类型的并发编程。许多已经可用,可以添加其他人。
特别是Java开发人员很可能有利于异步/等待风格,这些风格看起来类似于他们已经与期货合作的东西。这不仅仅是一个可比的替代,而是对我们所知道的重大改善。
并发Java代码附带了很多 检查例外, 防守锁定 策略和一般 样板 代码。所有这些都可以通过Coroutines改进,允许并发代码看 顺序,可管理和轻松可读。

看法

终于可以使用金属卷 Kotlin 1.3.。 KOTLIN团队将删除它的“实验”性质,并且金属公司将保持稳定状态。

今天(29/10/2018),Kotlin 1.3已被发布,其中包括稳定的金属版版本 -

请不要犹豫,取得联系,反馈总是很欣赏 - 如果你喜欢,请看看我的 推特 如果您对更多Kotlin的东西感兴趣。非常感谢。如果您想了解更多关于Kotlin的美丽功能,我强烈推荐这本书 kotlin在行动中 和我的 其他文章 to you.

西蒙

15 thoughts on “Kotlin Coroutines指南–Kotlin的并发编程

发表评论

您的电子邮件地址不会被公开。 必需的地方已做标记 *