异步编程技术
这里将介绍不同的异步编程实现.
作为程序员, 我们都面临着一个问题, 就是如何不让我们的程序阻塞. 无论我们是桌面开发, 移动开发, 甚至服务端开发.
有很多不同的实现来解决这个问题, 包括:
- -Threading
- -Callbacks
- -Futures, Promises
- -Reactive Extensions
- -Coroutines
我们先简明的看下前四种实现方式.
Threading
到目前为止, 线程是最为程序员所知的一种避免阻塞应用的实现.
- fun postItem(item: Item) {
- val token = preparePost()
- val post = submitPost(token, item)
- processPost(post)
- }
- fun preparePost(): Token {
- // makes a request and consequently blocks the main thread
- return token
- }
我们假设 preparePost 方法是一个很耗时的方法, 这样的话, 它将会阻塞用户交互. 我们可以把这个方法放在一个单独的线程中, 这样就避免了阻塞 UI 线程. 这是一个非常常见的技术, 但是它有一系列的缺点:
- 线程很耗资源, 它们要切换上下文
- 线程数量有限, 操作系统能启动的线程数量是有限的, 对于服务端来说, 这是一个很大的瓶颈
- 线程并不总是可用的, 比如 JavaScript 就不支持线程
- 编写好的代码并不容易, 容易出现各种竞争条件, 会陷入并发编程苦海
Callbacks
简单来说, 就是把函数作为参数传递给另一个函数, 一旦本身函数执行完成, 就执行传参的函数.
- fun postItem(item: Item) {
- preparePostAsync { token ->
- submitPostAsync(token, item) { post ->
- processPost(post)
- }
- }
- }
- fun preparePostAsync(callback: (Token) -> Unit) {
- // make request and return immediately
- // arrange callback to be invoked later
- }
这个看起来相对优雅的解决了问题, 但是同样有几个问题:
- 对于多重嵌套回调, 这将是一个非常难理解的方式
- 错误处理变得很复杂, 对于多重嵌套, 错误的传递和处理变得很复杂
Callbacks 在事件循环结构的语言中比较常见, 比如 JavaScript, 但更多的码农倾向于其它的解决方案, 比如 promises 或者 reactive extensions.
Futures, Promises
Futures,Promises, 不同的平台或语言有不同的叫法, 它是承若将在某个点返回一个叫 Promise 的对象, 简略操作如下所示:
- fun postItem(item: Item) {
- preparePostAsync()
- .thenCompose { token ->
- submitPostAsync(token, item)
- }
- .thenAccept { post ->
- processPost(post)
- }
- }
- fun preparePostAsync(): Promise<Token> {
- // makes request an returns a promise that is completed later
- return promise
- }
这个方式对于我们来说, 有一点挑战, 比如说:
- 不同的编程模型. 和 callback 类似, 从上而下的链式回调. 传统的编程结构, 比如循环, 错误处理都不在有效
- 不同的平台有不同的 API
- 具体的返回类型, 返回类型并不是我们实际的数据结构
- 错误处理变得复杂. 错误的传递处理并不清晰明确.
Reactive Extensions
响应式扩展 (Rx) 被 Eik Meijer 引入 C#, 虽然它确实存在于. NET 平台, 但直到被 Netflix 移植到 Java 平台, 才逐渐开始成为主流. 从这起, 各个平台实现自己的响应式扩展, 包括 JavaScript(RxJS).
Rx 的背后思想是把数据当作流来处理, 并且该流可以被观测. 实际上, Rx 只是观察者模式, 带有一系列的扩展来操作数据.
Rx 和 Futures 很类似, 但不同的是, 我们可以认为 Future 返回一个直接的元素, 而 Rx 返回流. 另一方面, 它提供了一中新的编程模型, 就像所宣传的口号那样:
everything is a stream, and it's observable
这意味着有不同的解决方法, 并且提供了不同的思路去写异步代码. 相比于 Futures 不同的是, Rx 被移植到多个平台, 我们有着一致的 API 体验. 包括 C#,Java,JavaScript, 或者其它实现了 Rx 的语言.
此外, Rx 引入了更友好的错误处理方式.
Coroutines
Kotlin 异步编程的方式是使用 coroutines, 这是一种可挂起的执行方式, 它可以在某一点挂起, 之后从这点恢复继续执行.
对于码农来说, 使用协程方法编写异步代码和编写同步代码没有什么不同, 这种编码模型并不存在什么挑战.
举个栗子:
- fun postItem(item: Item) {
- launch {
- val token = preparePost()
- val post = submitPost(token, item)
- processPost(post)
- }
- }
- suspend fun preparePost(): Token {
- // makes a request and suspends the coroutine
- return suspendCoroutine { /* ... */ }
- }
这段代码将会执行耗时工作, 但并不阻塞主线程. 带有 suspend 修饰的 preparePost 方法就是被成为可挂起函数. 就像上面描述的那样, 它可以在某个点挂起, 然后从该点恢复执行.
- 函数签名保持了一致, 仅增加了 suspend 修饰符. 返回值也是我们想要的类型
- 编写代码和我们以前一样, 并不需要特殊的语法
- 编程模型和 API 保持了一样, 我们可以继续使用循环, 异常处理, 不需要学习新的 API 集
- 它是平台无关的, 无论是运行在 JVM,JavaScript 平台, 还是其它, 我们写法都是一样的, 编译器负责适配各个平台.
Coroutines 并不是 Kotlin 发明的新概念, 它们已经存在了几十年了, 并且在其它语言非常流行, 比如 Go 语言. 需要注意的是, 虽然 Kotlin 实现了 Coroutines, 但大多数功能都是在库函数里边. 事实上, 除了 suspend 关键词, 没有其它的关键词被引入到语言中. 这个和 C# 有着较大的不同, C# 引入 async 和 await 做为语法的一部分. 而对于 Kotlin, 这些只是库函数.
来源: http://www.jianshu.com/p/2c8488e2cc0b