

Swift并发编程 - 理解 async 和 await
source link: https://kanchuan.com/blog/184-swift-async-await.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Swift并发编程 - 理解 async 和 await
原创 2023-06-30
本文是我学习 Swift 并发编程的第一篇笔记,文章从几个不太好理解的点,介绍了async 和 await 语法关键字的使用方法和内在含义。
async
使用 async 修饰的方法,称为异步方法(asynchronous method)。语法如下:
func my_func() async {
print("hello kanchuan.com")
}
如果一个方法既是异步又是 throwing 的,需要把 async 写在 throws 关键字前面。
单从语法上看,只是在普通函数中增加了 async 关键字。当普通方法使用 async 关键字修饰变成异步方法后,带来的影响是:
- 可以在异步方法的函数体内部使用 await 关键字(当然也可以不使用 await);
- 其它地方在调用这个异步方法时,需要使用 await 关键字。
await
await 表示此处是一个“possible suspension points",它指示编译器此处是可能的暂停点(注意这里是“可能”,后面会解释“可能”的含义)。当程序运行到 await 的代码时,会放弃当前线程(yielding the thread),“暂停”以等待异步方法的返回:
- 这里的暂停是指方法的暂停,而不是执行方法的线程的暂停,不然就失去了这么做的意义;
- await 会让出对当前线程的占有,这个线程可以被系统安排执行其它代码;
- await 等待的异步方法执行完成以后,从“暂停”状态恢复,将继续执行 await 语句后的代码。
不是在任意地方都能用 await
await 关键字只能出现在异步上下文环境中,目前有两种情况:
- 出现在 async 异步函数体内;
- 出现在 Task 任务的闭包中。
实际上,这两种情况都是在 Task 中。Task 是执行并发任务的基本单元,所有被 async 标记的函数都是通过 Task 管理的。
理解 Task
Task 是对线程的高度抽象封装,可以类比 GCD,只不过 GCD 是由 libdispatch 开源库提供的能力,并不是来自原生语法的支持。Task 是 自 Swift 原生支持的,在未来的使用中,将逐渐替代 GCD 成为 Swift 中完成异步任务的首选方案。
Task {} 是 Task.init 的简化形式,由 Task.init 创建的任务会继承调用线程的上下文并在调用线程中执行;而由 Task.detached 创建的任务是在独立的线程中执行,与调用线程完全独立,拥有自己的执行上下文和资源。
await 如何影响线程的调度?
在 Swift 的并发框架设计中,进一步弱化了线程的概念。线程的创建、调度完全交由并发框架隐藏并封装。下面用两个例子说明:
使用 Task.init 的例子
func my_func() async {
print("before sleep \(Thread.current)")
try? await Task.sleep(nanoseconds: 2 * 1_000_000_000)
print("after sleep \(Thread.current)")
}
func main_func() {
print("before main_func")
Task {
print("before Task \(Thread.current)")
await my_func()
print("after Task \(Thread.current)")
}
print("after main_func")
}
上述代码,main_func
方法确保是在主线程调用的(下面的例子也是如此),Task 闭包中调用了异步方法 my_func
,里面则执行了延迟。下面是上述代码的运行打印结果:
before main_func
after main_func
before Task <_NSMainThread: 0x600002ccc140>{number = 1, name = main}
before sleep <_NSMainThread: 0x600002ccc140>{number = 1, name = main}
after sleep <NSThread: 0x600002c89ac0>{number = 7, name = (null)}
after Task <_NSMainThread: 0x600002ccc140>{number = 1, name = main}
使用 Task.detached 的例子
修改上述代码,将 Task.init
改成 Task.detached
:
func my_func() async {
print("before sleep \(Thread.current)")
try? await Task.sleep(nanoseconds: 2 * 1_000_000_000)
print("after sleep \(Thread.current)")
}
func main_func() {
print("before main_func")
Task.detached { [self] in
print("before Task \(Thread.current)")
await my_func()
print("after Task \(Thread.current)")
}
print("after main_func")
}
运行观察打印输出:
before main_func
after main_func
before Task <NSThread: 0x600002ed1080>{number = 8, name = (null)}
before sleep <NSThread: 0x600002ed1080>{number = 8, name = (null)}
after sleep <NSThread: 0x600002ec43c0>{number = 4, name = (null)}
after Task <NSThread: 0x600002ec43c0>{number = 4, name = (null)}
可以注意到:
- 使用 await 标示后,改变了代码的执行线程;
- 每一个 await 就像一个分割屏障,将代码分成一个一个的独立「块」;
- 执行每一个「块」的线程是不确定的,由并发框架调度,有可能在一个线程,也有可能在不同的线程。
基于以上原理,在这种执行线程不确定的情形下要尽量避免使用信号量、锁等传统同步机制,而是利用 Swift 并发框架本身的特性。如下示例就会造成死锁。
let lock = NSLock.init()
func my_func() async {
lock.lock()
print("before sleep \(Thread.current)")
try? await Task.sleep(nanoseconds: 2 * 1_000_000_000)
lock.unlock()
print("after sleep \(Thread.current)")
}
for i in 0..<5 {
Task {
await my_func()
}
}
理解 possible suspension points 中的 possible
之所以加上 "possible" 这个修饰词,是因为并非所有的 await 关键字都会导致真正的暂停,有一些特例情况。
在上述示例中,my_func
里 调用了 Task.sleep,实际上,我们也完全可以在 my_func
同步调用函数。将上述例子修改如下:
func my_func() async {
print("in my_func \(Thread.current)")
}
func main_func() {
print("before main_func")
Task {
print("before Task \(Thread.current)")
await my_func()
print("after Task \(Thread.current)")
}
print("after main_func")
}
此时,我们声明 my_func 是异步函数,但它里面执行的却都是同步代码。打印结果如下:
before main_func
after main_func
before Task <_NSMainThread: 0x600001830a00>{number = 1, name = main}
in my_func <_NSMainThread: 0x600001830a00>{number = 1, name = main}
after Task <_NSMainThread: 0x600001830a00>{number = 1, name = main}
可以看到 Task 闭包中的代码是完全同步执行的,并没有发生「暂停」。
参考文档:
Swift 并发初步
Swift.org:Concurrency
相关文章:
iOS系统如何获取用户的本机手机号
iOS Link Map
iOS Flutter MethodChannel 双向通信
iOS Appium自动化测试框架原理简析
iOS安全:Tweak clang: warning: libstdc++ is deprecated
Recommend
-
50
什么是async? 现在面对日常工作时,总避免不了面对异步操作带来的一些麻烦。在时代演变的过程中,处理异步的方法有许多种:回调函数、Promise 链式语法、Generator 函数到现在比较流行的 async 函数。那什么是 async 呢? async
-
15
You all might know that async/await is accepted and is available in the main snapshots! Let’s get our hands dirty by trying out some basic example of async/await in Swift. Prerequisites Xcode 12.3 La...
-
9
.NET Web应用中为什么要使用async/await异步编程?前言1.什么是async/aw...
-
6
Swift 5.5 Brings Async/Await and Actor Support Jun 13, 2021 2...
-
8
This year, WWDC came with a bunch of new features and updates. Maybe one of the most expected was the introduction of the new concurrency system by using async/await syntax. This is a huge improvement in the way that we write asynchronous cod...
-
11
Making Network Requests with Async/await in Swift Traditionally, when we want to make a network request, we must use the closure-based URLSession APIs to perform the request asynchronously so that our apps can be respons...
-
6
Using Async/Await with AWS Amplify Libraries for Swift by Kyle Lee | on 03 NOV 2022 | in
-
7
Swift 中的 Async/Await ——代码实例详解 作者:Swift君 2022-11-21 09:01:00 Swift 中的 async-await 允许结构化并发,这将提高复杂异步代码的可读性。不再需要完成闭包,而在彼此之后调用多个异步方法的可读性也...
-
4
异步这个概念在不同语境下有不同的解释,比如在一个单核CPU里开启两个线程执行两个函数,通常认为这种调用是异步的,但对于CPU来说它是单核不可能同时运行两个函数,不过是由系统调度在不同的时间分片中执行。一般来说,如果两个工作能同时进行,就认为是异步的。在...
-
14
How async/await works internally in Swift How async/await works internally in Swift Published on 28 Sep 2023 async/await in Swift was introduced with iOS 15, and...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK