2025-02-07 10:12:38
,某些文章具有时效性,若有错误或已失效,请在下方留言。Whenever we use await
to call an async function, we mark a potential suspension point in our code – we’re acknowledging that it’s entirely possible our function will be suspended, along with all its callers, while the work completes.
每当我们使用 await
调用异步函数时,我们都会在代码中标记一个潜在的暂停点——我们承认,在工作完成时,我们的函数及其所有调用者完全有可能被挂起。
You can see the suspension chain in action with code like this:
您可以使用如下代码查看悬架链的运行情况:
func countFavorites() async throws {
let favorites = try await decodeFavorites()
print("Downloaded \(favorites.count) favorites.")
}
func decodeFavorites() async throws -> [Int] {
let data = try await loadFavorites()
return try JSONDecoder().decode([Int].self, from: data)
}
func loadFavorites() async throws -> Data {
let url = URL(string: "https://hws.dev/user-favorites.json")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
When that code runs, countFavorites()
will call decodeFavorites()
, which in turn will call loadFavorites()
. Inside loadFavorites()
is our actual networking code, which will suspend while the work happens, and in turn cause all three functions – loadFavorites()
, decodeFavorites()
, and countFavorites()
– to suspend.
当该代码运行时,countFavorites()
将调用 decodeFavorites(),
后者又将调用 loadFavorites()
。loadFavorites()
内部是我们的实际网络代码,它将在工作进行时挂起,进而导致所有三个函数 — loadFavorites()
、decodeFavorites()
和 countFavorites()
— 暂停。
In terms of performance, this is not free: synchronous and asynchronous functions use a different calling convention internally, with the asynchronous variant being slightly less efficient.
就性能而言,这并不是免费的: synchronous 和 asynchronous functions 在内部使用不同的调用约定,asynchronous 变体的效率略低。
The important thing to understand here is that Swift cannot tell at compile time whether an await
call will suspend or not, and so the same (slightly) more expensive calling convention is used regardless of what actually takes place at runtime.
这里需要理解的重要一点是,Swift 无法在编译时判断 await
调用是否会挂起,因此无论运行时实际发生什么,都会使用相同的(稍微)昂贵的调用约定。
With our networking code, for example, it’s entirely possible the system chooses not to suspend if the data is already cached from a previous load, or if the network connection is offline – we don’t know, and neither does Swift until the code actually runs.
例如,对于我们的网络代码,如果数据已经从之前的加载中缓存,或者网络连接处于离线状态,则系统完全有可能选择不暂停 – 我们不知道,在代码实际运行之前,Swift 也不知道。
What happens at runtime depends on whether the call suspends or not:
运行时发生的情况取决于调用是否暂停:
- If a suspension happens, then Swift will pause the function and all its callers, which has a small performance cost. These will then be resumed later, and ultimately whatever performance cost you pay for the suspension is like a rounding error compared to the performance gain provided by async/await even existing.
如果发生暂停,则 Swift 将暂停该函数及其所有调用方,这会降低性能。这些将在稍后恢复,最终,您为暂停支付的任何性能成本与 async/await 提供的性能增益相比,即使存在,也类似于舍入误差。 - If a suspension does not happen, no pause will take place and your function will continue to run with the same efficiency and timings as a synchronous function.
如果未发生暂停,则不会发生暂停,您的函数将继续以与同步函数相同的效率和计时运行。
That last part carries an important side effect: using await
will not cause your code to wait for one runloop to go by before continuing.
最后一部分有一个重要的副作用:使用 await
不会导致您的代码等待一个 runloop 过去后再继续。
It’s a common joke that many coding problems can be fixed by waiting for one runloop tick to pass before trying again – usually seen as DispatchQueue.main.async { … }
in Swift projects – but that will not happen when using await
, because the code will execute immediately.
一个常见的笑话是,许多编码问题可以通过等待一个 runloop 滴答通过后再尝试来解决 – 通常见于 DispatchQueue.main.async { … }
Swift 项目中 – 但是在使用 await
时不会发生这种情况,因为代码将立即执行。
So, if your code doesn’t actually suspend, the only cost to calling an asynchronous function is the slightly more expensive calling convention, and if your code does suspend then any cost is more or less irrelevant because you’ve gained so much extra performance thanks to the suspension happening in the first place.
因此,如果您的代码实际上没有挂起,则调用异步函数的唯一成本是略高的调用约定,如果您的代码确实挂起,则任何成本或多或少都无关紧要,因为由于首先发生挂起,您获得了如此多的额外性能。