kotlin异步
与期货/承诺(例如Reactor的Mono或Java的CompletableFuture)相比,介绍Kotlin协程
是否想直接进入代码? 去 这里 检查 一下 。
介绍
在阅读有关Kotlin的协程的同时,我认为这真的很有帮助
将其与其他异步编程技术进行比较,特别是
Java中广泛使用的Future / Promises方法。
尽管Kotlin文档非常好而且很全面,但我还是觉得缺少一个“真实世界”的例子。 这是试图填补这一空白的尝试。
不要期望全面了解协程如何工作,也不要期望Java的CompletableFuture
或Reactor的Mono
工作。
这个想法是要选择一个非常简单的虚构问题,然后加以实施
与不同的方法和库进行比较。 这是
该主题的介绍性材料。
现在,让我们讨论一下伪造的问题。
问题陈述
在您需要从一个地方获取数据,进行一些处理并将其发送到另一个地方的问题上,谁从来不需要解决? 这里的虚假问题基本上就是这个。
假设我们从事电子商务,并且希望从HTTP端点获取最新的订单ID,然后将此ID发送给另外两个端点:
- 向我们提供有关订单中物品的库存信息,以及;
- 给我们计算订单的交付成本。
然后,我们需要将所有合并的信息公开给其他消费者。
我们还假设获取最新顺序后的操作在处理上较慢,我们希望并行运行它们。
我们想要实现以下内容:
期货/承诺方式
对于未来/承诺方法,我们将使用两个非常流行的API /库来处理Java中的异步编程: CompletableFuture
和Mono
。
从JDK 1.8开始, CompletableFuture
在标准Java库中可用,并且在最新版本中进行了一些改进。 因为它是标准库的一部分,所以它是愿意支持非阻塞过程的Java库的默认选择,并且在标准库中的某些其他API中使用,例如java.net.HTTPClient
。
Mono
是Project Reactor库中的CompletableFuture
等效项。 Project Reactor是JVM上非常流行的非阻塞React式库。 最近,它的采用量一直在增长,这主要是因为它已集成到Spring Framework中。
它也已被其他JVM库采用。
现在,让我们看看实现如何与这些库一起使用。 首先,使用CompletableFuture
实现:
fun completableFuture () : CompletableFuture<CombinedResult> {
val orderIdFuture: CompletableFuture<String> = fetchMostRecentOrderId()
return orderIdFuture
.thenCompose { orderId ->
val deliveryCostFuture: CompletableFuture<String> = fetchDeliveryCost(orderId)
val stockInformationFuture: CompletableFuture<String> = fetchStockInformation(orderId)
deliveryCostFuture.thenCombine(stockInformationFuture) { r1, r2 -> CombinedResult(r1, r2) }
}
}
fun fetchDeliveryCost () : CompletableFuture<String> { ... }
fun fetchStockInformation () : CompletableFuture<String> { ... }
现在, Mono
实现:
fun mono () : Mono<CombinedResult> {
val orderIdFuture: Mono<String> = fetchMostRecentOrderId()
return orderIdFuture
.flatMap { orderId ->
val deliveryCostFuture: Mono<String> = fetchDeliveryCost(orderId)
val stockInformationFuture: Mono<String> = fetchStockInformation(orderId)
deliveryCostFuture.zipWith(stockInformationFuture) { r1, r2 -> CombinedResult(r1, r2) }
}
}
fun fetchDeliveryCost () : Mono<String> { ... }
fun fetchStockInformation () : Mono<String> { ... }
它们非常相似,对不对? 这就是为什么他们在同一部分下。 它们都遵循与Future / Promises相同的异步编程技术。
您可以看到,唯一的区别(除了返回类型之外)是所使用的方法,即使它们的机制完全相同:
-
thenCompose
/flatMap
用于组成两个不同的promise,基本上意味着“在此promise完成后,返回另一个promise”; -
thenCombine
/zipWith
用于组合两个不同的诺言,基本上意味着“给我一个新的诺言,该诺言将在两个诺言都完成后再完成”。
我们没有讨论比较这两个库的详细信息,但是对于基本用例,您可以看到它们遵循相同的方法。
协程方法
Kotlin协程本质上是轻量级线程。 Kotlin语言提供了一种高级构造( 挂起函数 )和一个库kotlinx.coroutines
库,以便以与大多数开发人员习惯的常规命令式编程非常相似的方式处理异步编程。
可以将其视为Kotlin执行async/await
方式,该方式可以在其他语言(例如C#或JavaScript)中使用。
现在,回到主题。 这是使用kotlinx.coroutines
库中的挂起函数和构造的kotlinx.coroutines
工作方式:
suspend fun coroutines () : CombinedResult = coroutineScope {
//Returns a String directly, no future object
val orderId: String = fetchMostRecentOrderId()
//Note the Deferred type indicating this is a future
val deliveryCostFuture: Deferred<String> = async { fetchDeliveryCost(orderId) }
val stockInformationFuture: Deferred<String> = async { fetchStockInformation(orderId) }
//Awaiting for completion of previous parallel executed fuctions
CombinedResult(deliveryCostFuture.await(), stockInformationFuture.await())
}
suspend fun fetchDeliveryCost () : String { ... }
suspend fun fetchStockInformation () : String { ... }
您可以看到它似乎与我们大多数人习惯的命令式编程风格非常相似,并且这是我们从一开始就学习编码的方式。 一些注意事项:
- 所有功能都是暂停功能,如关键字
suspend fun
(第suspend fun
行)所示。 挂起函数基本上是Kotlin对函数进行着色的方式,以告知编译器它可以运行异步操作。 - 挂起函数只能在另一个挂起函数或
coroutineScope
建造者 ;
- 第三行被挂起,这意味着执行将被挂起(非阻塞!),等待
fetchMostRecentOrderId
的返回,而fetchMostRecentOrderId
直接返回一个String
。 - 通过将
async
函数与挂起函数一起使用,我们可以在后台运行它,这将返回Deferred对象(就像一个Promise),如第6和第7行所示; - 通过在Deferred对象上调用await函数,将第10行再次挂起,直到两个Deferred对象的结果完成为止。
- 我们需要将函数包装到coroutineScope中,以便在默认情况下按顺序执行协程代码,从而用async进行并行处理。
Kotlin对async / await的回答与JavaScript稍有不同。 例如,这将是JS中的类似功能:
async function jsAsync () {
const orderId = await fetchMostRecentOrderId(); //returns String
const deliveryCostFuture = fetchDeliveryCost(orderId); //returns Promise
const stockInformationFuture = fetchStockInformation(orderId); //returns Promise
return new CombinedResult( await deliveryCostFuture, await stockInformationFuture);
}
async function fetchDeliveryCost () { ... }
async function fetchStockInformation () { ... }
差异是细微但重要的,尽管Kotlin协程默认情况下是顺序的,而JavaScript协程默认情况下是并发的:
- 在第2行中,我们需要显式使用await,而在Kotlin中则无需这样做。
- 在第4行和第5行,不需要使用任何
async
构造,因为JS中的异步函数会返回Promise。
结论
这是一个非常简单的流程,仅用于说明编程风格/技术的差异。
有人可能会争辩说,对于这个特定的示例,使用Futures / Promise方法使代码更简洁,易于理解,这将是样式问题。 但是,随着代码变得越来越复杂,并且我们有越来越多的要求,使用Futures / Promise可能会变得很难管理。
您可以通过比较两种方法的更复杂的示例在此处查看良好的帖子。
甚至即使我们遇到相同的问题并尝试按顺序实施它,期货/承诺也会使事情变得复杂。 正如所看到的在这里 ,我们需要为了利用引进的中间类型的份额方面,在这种情况下Kotlin对类型 。
我还整理了一个简单的Kotlin Spring Boot项目 ,您可以在其中运行代码并尝试使用此处讨论的各种技术。
这是对该主题的简要介绍,并且还有许多要深入探讨的协程,为此,我建议使用以下材料:
- Kotlin 有关协程的官方文档;
- Kotlin文档中的“异步编程技术”页面 ;
- 协程设计文档的“异步编程样式”部分 ;
- 为什么协程实现既不像JavaScript那样也不像Go一样? 您可以在这里看到原因。
感谢您的阅读,直到这里,我希望您喜欢它。
我要非常感谢在这篇文章中提供了友好的语气和早期反馈的朋友和同事。 你们都摇滚!
先前发布在 https://medium.com/@danilo_barboza/kotlin-asynchronous-programming-styles-compared-347470b95f3a
翻译自: https://hackernoon.com/asynchronous-programming-techniques-with-kotlin-fg9l3wjn
kotlin异步