await 的非阻塞式挂起机制是基于 协程 和 事件循环(event loop)等底层概念实现的。在 Swift 中,await 并不会像传统的阻塞式调用那样让线程停住,而是通过将当前任务挂起(suspended)并允许其他任务继续执行来实现非阻塞效果。具体来说,await 让你在等待结果的同时不会阻塞线程,允许其他任务继续并发执行。
我们可以从以下几个角度来理解 await 的工作原理:
1️⃣ 挂起与恢复
- 挂起(Suspension)
当你调用一个 async 函数并在某个位置使用 await 时,这个 await 会告诉编译器当前函数可能会被挂起。这个函数暂停执行,释放当前线程的控制权,让其他任务得以执行。
- 恢复(Resumption)
当挂起的任务完成时(例如,异步网络请求、延时操作等),系统会恢复任务的执行,继续执行 await 后面的代码。
这其实是一个 状态机 的机制,任务会被挂起,在异步操作完成后恢复。
2️⃣ 事件循环与任务队列
为了让 await 非阻塞,Swift 使用了 事件循环 或者说 任务队列 来调度任务。当你在 async 函数内部使用 await 时,编译器会将当前任务(函数)加入一个队列,而不是让当前线程挂起。
事件循环/任务队列
- 当你调用一个异步函数(例如
async let或await fetchData())时,任务被加入到 事件循环 或 任务队列。 - 当前线程(通常是主线程)会继续执行其它代码,不会被阻塞。
- 当异步操作完成时,事件循环会恢复这个任务并继续执行。这个恢复的操作会在其他任务完成后触发。
3️⃣ 使用 Task 和 Await 的实现
我们先来看一个非常简单的 async/await 示例:
swift
复制编辑
func fetchData() async -> String {
// 模拟异步操作,例如从网络获取数据
await Task.sleep(2 * 1_000_000_000) // 模拟延时操作
return "数据加载完毕"
}
Task {
print("开始获取数据...")
let result = await fetchData() // 这里会挂起当前任务,等待结果
print(result)
}
在上面的代码中:
fetchData()是一个异步函数,调用它时会挂起当前的Task。await会将当前Task暂停,并允许其他代码在同一线程中执行。- 当
Task.sleep()完成后,Task会恢复,返回"数据加载完毕"。
这里,Task.sleep() 是一种模拟操作,它并没有阻塞当前线程。而是将控制权交还给了运行时,执行其他任务,直到 sleep 完成后,原来的任务才会继续。
4️⃣ 底层实现
- 挂起和恢复的机制
await 的非阻塞式挂起通常通过 回调、状态机 和 上下文切换 实现。底层通常会用类似于 纤程(fiber) 或 协程 的机制来管理这个任务的暂停和恢复。具体过程如下:
- 挂起:当一个
await被调用时,函数会返回给调用者一个 “挂起点” ,并且将当前任务加入到队列中。这个任务的状态会被保存,等到结果返回时,任务再从队列中取出。 - 恢复:一旦异步操作完成,底层调度器会通知相关的任务恢复,重新调度任务的执行。这是通过事件循环来实现的,异步操作会将事件触发的回调加入到队列中,等待当前线程空闲时执行。
- 任务队列
任务的调度机制非常类似于事件循环,它是 基于队列的,意味着你不需要等待每一个任务的执行结果,而是将它们按照顺序加入到队列中,任务会在后续空闲时执行。
例如,在以下代码中:
swift
复制编辑
Task {
let result1 = await fetchData() // 任务1
let result2 = await fetchData() // 任务2
}
- 任务 1 会被加入任务队列,并挂起。
- 在 任务 1 挂起期间,其他任务可以继续执行。
- 当 任务 1 完成时,队列会恢复 任务 2,并且继续执行。
- 线程池和异步任务
await 的执行过程中,线程池会帮助管理任务的执行。线程池通过 调度器 和 优先级队列 来确保异步任务能在合适的时机恢复并继续执行。
5️⃣ 核心原理:
1. 任务挂起(Suspension)
- 当你调用
await,当前的函数被挂起,等待异步任务完成。 - 这时,当前线程会继续执行其他任务,并不会被阻塞。
2. 任务恢复(Resumption)
- 一旦异步操作完成(比如网络请求返回),被挂起的任务会从 挂起状态 恢复,继续执行后续代码。
3. 任务队列
- 异步操作完成后,任务恢复的执行顺序是由事件循环控制的。通过事件循环,任务按顺序执行,避免了死锁和不必要的等待。
6️⃣ 总结
await本质上是通过 任务队列 和 状态机 来管理任务的挂起和恢复,它实现了非阻塞的异步执行。- 通过
await,我们可以暂停当前的任务,释放当前线程的控制权,让其他任务得以执行。 - 底层通过 回调 和 事件循环 机制来确保任务的有序执行。
这样,非阻塞式挂起 可以确保主线程不会被阻塞,而异步任务仍然能在背后顺利完成,提升程序的响应性。