这一次 WWDC 2021,让我们很多苹果开发者眼前一亮的是 Swift 中的并发特性:async/await
,虽然这个概念并不新。
您可能会有以下疑问:
请别急,现在来和 Ficow 一起揭晓答案吧~
我们常见的同步和异步调用的模式如下:
同步执行
:线程按代码的先后顺序依次执行,前面的代码未执行结束时,线程不会执行后续的代码;
异步执行(回调方式)
:线程执行到异步代码的时候,会同时继续执行异步代码和后续的同步代码,当异步代码完成时线程就会调用回调函数中的代码;
这是一个常见的异步执行的示例,列表中的图标通常通过异步的方式获取:
现在让我们来拆解这个示例,示例中的子任务一个接一个地进行:
其中有一些比较耗时的操作,它们应该异步执行:
其中,获取缩略图的方法的实现部分如下所示:
在回调函数中,多级的条件判定逻辑非常容易导致错误
。比如,红色箭头指向的地方就未对失败情况进行正确的处理,而且是 两处
都漏掉了!如果这种情况发生了,列表上的图标就不会被加载出来,而是一直显示一个加载指示器。
现在我们再回头一看,为了异步加载缩略图,居然就需要 写这么多代码
,而且代码这么容易就出错了,不开心啊!
我想,您现在应该也已经发现我们熟悉的异步回调模式存在的 明显缺陷
了。那么,如何改进呢?
使用标准库中的 Result
类型也许是一种改进方法。然而,问题依然存在,而且代码还变得更长了:
也许还有人会说,把 if-else
的最后一个 else
稍加调整就可以减少 guard-else
的缩进层级,然后就可以缓解这种情况了。
这也是一种解决办法,然而它不能从根本上解决问题,而且这个方法还要开发者牢记于心才行。
那么,有没有什么强制手段来改进这种问题呢?
比如,让编译器帮忙检查异步代码
的逻辑是否存在问题?
而且,我们想让异步代码变得 更简单、安全
,有没有可能呢?
新技术的出现一定是为了更好地解决已有的问题,而且旧技术往往无法很好地处理这些问题。
为了解决常见的异步处理问题,Swift 中的 async/await
应运而生。它可以有效地精简代码、消灭回调地狱,而且更充分地利用 CPU 资源!
Swift 中的 async/await
本质上来说,就是协程。
对于协程和线程的区别,维基百科的解释大致如下:
- 协程
很像
线程;- 协程是协同多任务,而线程是典型的抢先式多任务,所以
协程支持并发,但是不支持并行
;- 协程相对于线程具有这些优势:
切换协程不涉及系统调用或者任何阻塞调用
、不需要使用同步原语
(如:互斥、信号量等)来保护临界区;
请注意:并发是同时管理多个任务,并行是同时执行多个任务;
实际上,很多编程语言都已经支持协程,比如:Go Coroutines, Kotlin Coroutines。
甚至 ObjC 也已经支持协程,这是阿里巴巴开源的协程库:alibaba/coobjc,而且此库提供的协程也基于 async/await 模型。
此外,该开源协程库的文档开篇就提到了 iOS 异步编程的问题,而且总结得非常全面:
可能是 Swift 进化太慢了,所以也有人急不可耐。SwiftCoroutine 就是一个很好的例子:
如果仔细观察这个库的用法,您就会发现它好像并不易用,或者说接口的设计并不简洁、自然。
为什么这么说呢?让我们一起来看一下 Swift 语言原生的 async/await
吧~
同样是之前的获取缩略图的示例,如果使用 Swift 原生的 async/await
重写之后,变成了这样:
请注意: 同步执行 thumbnailURLRequest(for: id)
时,当前线程会被阻塞。执行 try await URLSession.shared.data(for:request)
时,当前协程会被挂起(suspend),此时不会被阻塞,所以线程还可以去执行其他任务。
对比之前的异步回调函数版本,使用 async/await
的版本具有以下优势:
throw
来进行错误处理,不会再漏掉任何错误情况;
不仅方法可以异步执行,只读属性
也可以!
如果此属性会抛出错误,您还可以这样定义:
get async throws {
return try await self.byPreparingThumbnail(ofSize: .init(width: 40, height: 40))
}
甚至,连 for 循环也可以异步执行了呢~
如果您对此感兴趣,可以参考 Meet AsyncSequence。
如果您需要处理大量的异步操作,可以参考 Explore structured concurrency in Swift。
正常的方法调用会一直占用线程资源,直到方法调用结束(return),然后线程的控制权才会返回到该方法的调用方。也就是说,正常的方法只能通过完成调用才可以放弃对线程的控制权。此过程如下图所示:
异步方法调用可以选择挂起并把线程的控制权交给系统(不是交给此方法的调用方
),让系统来决定如何使用该线程(这就是所谓的协同式多任务)。之后,在某个适当的时机,系统又会把线程(可能不是之前的线程)的控制权交回给此异步方法。当然,这个方法然后可以选择再次挂起。此过程如图所示:
这里还有一些注意事项:
async
关键字的方法支持异步执行(可以被挂起);await
关键字的方法表明异步方法 可能
被挂起,但是它不一定会被挂起。数据竞争
(Data Race)问题。对于数据竞争问题,您可以参考 Protect mutable state with Swift actors 中提供的解决方法。
现在,异步测试代码可以用 async/await 来重写:
用 async/await 重写后,测试方法变得如此简洁(就 1
行):
异步回调闭包是很常见的代码,比如:
可以使用 async
来重写:
更多具体的用法,请参考:
形如这些支持异步回调闭包的SDK接口:
现在都已经拥有了支持 async
的版本:
SDK 常见的代理方法,比如:
现在也可以用 async/await
来重写:
关于SDK提供的支持 async
的新接口方法,这是一些相关的 WWDC 2021 视频:
首先,这是一段使用了回调闭包的代码,它从 CoreData 中读取数据,然后执行回调:
如果想要对这个方法进行封装,我们会遇到一点阻碍。该如何连接回调方法和 async 呢?
其实,这是一个常见的异步执行模式,和前文中的示例非常相似:
所以,很明显,这里缺少了一个连接的步骤。
Swift 为此引入了 Continuation
这个概念。这里,我们可以借助 CheckedContinuation 来完成这个连接:
使用 CheckedContinuation
需要注意:
1
次;忘记调用
Continuation 的 resume 方法,Swift 运行时会报错;此外,常见的代理方法也可以用 Continuation 连接以实现异步处理:
用 CheckedContinuation
连接后的效果:
让我们再来回顾一下可以应用 Continuation 的常见异步执行模式:
如果想了解更多 Swift 并发的细节,请参考: Swift concurrency: Behind the scenes
async/await
来重写旧的异步代码;async
的版本;
其实,Swift 一直在进化中,而且这个过程还将继续下去。
在 《Swift 进阶》的错误处理这一章的结尾中,作者对 Swift 中的错误处理进行了展望:
如今,async/await 风格的并发模型
和 支持异步函数的 throws 错误处理
在 Swift 中皆已实现。
实际上,就单从并发这一个方面来看,Swift 都还有很长的路要走。这是 Swift 之父 Chris Lattner 对于 Swift 并发的构思(Swift Concurrency Manifesto),从这里我们也能看得出,Swift 还会继续成长,直到它比任何 competitor 都要伟大。
这是 Swift Concurrency Manifesto 的内容概览,可以看出 Swift 已经完成了 Part 1 和 Part 2:
其中 Learning from other concurrency designs 分析和探讨了诸多语言中的并发设计,可以帮助您拓展眼界,值得一看~
从最初的多进程
任务调度系统,到后来的多线程
任务调度系统,再到现在的协程
调度系统,可被利用的资源一再被细分。
如今已是 多核计算机时代
,如何更高效地利用 CPU 的多核资源
是现代编程语言都要面对的一个长期问题。
Swift 作为一门年轻的现代编程语言,有很多优秀的先例可供学习,所以现在成长迅速。
然而,之后的路要怎样走就是一个未知数了。所以,在可以预见的未来,Swift 在并发模式上应该不会有太大的变化。
不管怎样,我们都应该真诚地感谢 Swift 团队和开源社区对 Swift 的巨大贡献。❤️ 如今,我们可以以一种更加简洁、高效且安全
的方式来实现异步任务。
参考内容:
Meet async/await in Swift
Swift Concurrency Manifesto
Go Coroutines
Kotlin Coroutines
alibaba/coobjc
SwiftCoroutine
Meet AsyncSequence
Explore structured concurrency in Swift
Swift concurrency: Behind the scenes
觉得不错?点个赞呗~
本文链接:Swift 中的并发之 async/await —— WWDC 2021
转载声明:本站文章如无特别说明,皆为原创。转载请注明:Ficow Shen's Blog