Kotlin 、协程、结构化并发

在 Kotlin 1.1 也就是 2017年初, 首次推出协程作为实验性质的特性开始,我们一直在努力向程序员解释协程的概念,他们过去常常使用线程理解并发,所以我们举的例子和标语是"协程是轻量级线程"。

此外,我们的关键 api 被设计为类似于线程 api,以简化学习曲线。这种类比在小规模例子中很适用,但是它不能帮助解释协程编程风格的转变。

当我们学习使用线程编程时,我们被告知线程是昂贵的资源,不应该到处创建它们。一个优雅的程序通常在启动时创建一个线程池然后使用它们搞些事情。有些环境(尤其是 iOS)甚至说"不赞成使用线程"(即使所有的东西仍然在线程上运行)。它们提供了一个系统内的随时可用的线程池,其中包含可向其提交代码的相应队列。

但是协程的情况不同。它可以非常方便地创建很多你需要的协程,因为它们非常廉价。让我们看一下协程的几个用例。

异步操作(Asynchronous operations)

假设你正在写一个前端 UI 应用(移动端、web 端或桌面端——对于这个例子并不重要),并且需要向后端发送一个请求,以获取一些数据并使用结果更新 UI 模型。我们最初推荐这样写:

fun requestSomeData() {
    launch(UI) {
        updateUI(performRequest())
    }
}

这里,我们使用 launch(UI) 在 UI 上下文中启动一个新的协程,调用performRequest 挂起函数对后端执行异步调用,而不阻塞主 UI 线程,然后使用结果更新 UI。每个 requestSomeData 调用都创建自己的协程,这很好,不是吗?它和 C# JS 和 GO 中的异步编程并没有太大的不同。

但是这里有个问题。如果网络或后端出现问题,这些异步操作可能需要很长时间才能完成。此外,这些操作通常在一些 UI 元素(比如窗口或页面)的范围内执行。如果一个操作花费的时间太长,通常用户会关闭相应的 UI 元素并执行其他操作,或者更糟糕的是,重新打开这个 UI 并一次又一次地尝试该操作。但是前面的操作仍然在后台运行,当用户关闭相应的 UI 元素时,我们需要某种机制来取消它。在 Kotlin 协程中,这导致我们推荐了一些非常棘手的设计模式,人们必须在代码中遵循这些模式,以确保正确处理这种取消。此外,你必须是中记住指定适当的上下文,否则 updateUI 可能会被错误的线程调用,从而破坏 UI。这是容易出错的。一个简单的launch{ ... } 很容易写出来,但是你不应该写成这样。

在更哲学的层面上,很少像线程那样"全局"地启动协程。线程总是与应用程序中的某个局部作用域相关,这个局部作用域是一个生命周期有限的实体,比如 UI 元素。因此,对于结构化并发,我们现在要求在一个协程作用域中调用 launch,协程作用域是由你的生命周期有限的对象(如 UI 元素或它们相应的视图模型)实现的接口。你实现一次协程作用域后, 你会发现,在你的 UI 类中有一个简单的 launch{ … } ,然后你写很多遍,变得极容易写又正确:

fun requestSomeData() {
    launch {
        updateUI(performRequest())
    }
}

注意,协程作用域的实现还为 UI 更新定义了适当的协程上下文。对于一些比较少见的情况,你需要一个全局协程,它的生命周期受整个应用生命周期限制,我们现在提供了 GlobalScope (全局作用域)对象,因此以前全局协程的launch{ … } 变成了 GlobalScope.launch { … } ,协程的"全局"含义变得直观了。

并行分解(Parallel decomposition)

我已经就 Kotlin 协程进行了多次 [讨论],,下面的示例代码展示了如何并行加载两个图片并在稍后将它们组合起来——这是一个使用 Kotlin 协程并行分解工作的惯用示例:

suspend fun loadAndCombine(name1: String, name2: String): Image { 
    val deferred1 = async { loadImage(name1) }
    val deferred2 = async { loadImage(name2) }
    return combineImages(deferred1.await(), deferred2.await())
}

不幸的是,这个例子在很多层面上都是错误的。挂起函数loadAndCombine 本身将从一个已经启动的执行更大操作的协程内部调用。如果这个操作被取消了呢?然后加载这两个图片仍然没有收到影响。这不是我们想从可靠代码中的得到的,特别是如果这些代码是许多客户端使用后端服务的一部分。
我们推荐的解决方案是写成这样async(conroutineContext){ … } ,以便在子协程中加载两个图片,当父协程被取消时,子协程将被取消。
它仍然不完美。如果加载第一个图片失败,那么 deferred1.await() 将抛出相应的异常,但是加载第二个图片的第二个 async 协程仍然在后台工作。解决这个问题就更复杂了。
我们在第二个用例中看到了同样的问题。一个简单的 async { … } 很容易写,但是你不应该写成这样。
使用结构化并发,async 协程构建器就像 luanch 一样,变成了协程作用域上的一个扩展。你不能再简单的编写 async{ … } ,你必须提供一个作用域。并行分解的一个恰当的例子是:

suspend fun loadAndCombine(name1: String, name2: String): Image =
    coroutineScope { 
        val deferred1 = async { loadImage(name1) }
        val deferred2 = async { loadImage(name2) }
        combineImages(deferred1.await(), deferred2.await())
    }

你必须将代码封装到 coroutineScope { ... } 块中,这个块为你的操作及其范围建立了边界。所有异步协程都成为这个范围的子协程,如果该作用域因为异常导致失败或被取消了,它所有的子协程也将被取消。

创作不易喜欢的话记得点赞+关注哦

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容