2025-02-07 10:16:43
,某些文章具有时效性,若有错误或已失效,请在下方留言。Although Swift functions are synchronous by default, we can make them asynchronous by adding one keyword: async
. Inside asynchronous functions, we can call other asynchronous functions using a second keyword: await
. As a result, you’ll often hear Swift developers talk about async/await as a way of coding.
虽然 Swift 函数默认是同步的,但我们可以通过添加一个关键字来使它们异步:async
。在异步函数中,我们可以使用第二个关键字 await
调用其他异步函数。因此,您经常会听到 Swift 开发人员谈论 async/await 作为一种编码方式。
So, this is a synchronous function that rolls a virtual dice and returns its result:
因此,这是一个同步函数,用于掷虚拟骰子并返回其结果:
func randomD6() -> Int {
Int.random(in: 1...6)
}
let result = randomD6()
print(result)
And this is an asynchronous or async function:
这是一个异步或异步函数:
func randomD6() async -> Int {
Int.random(in: 1...6)
}
let result = await randomD6()
print(result)
The only part of the code that changed is adding the async
keyword before the return type and the await
keyword before calling it, but those changes tell us three important things about async functions.
代码中唯一更改的部分是在返回类型之前添加 async
关键字,在调用之前添加 await
关键字,但这些更改告诉我们有关异步函数的三个重要事项。
First, async
is part of the function’s type. The original, synchronous function returns an integer, which means we can’t use it in a place that expects it to return a string. However, by marking the code async
we’ve now made it an asynchronous function that returns an integer, which means we can’t use it in a place that expects a synchronous function that returns an integer.
首先,async
是函数类型的一部分。原始的 synchronous 函数返回一个整数,这意味着我们不能在期望它返回字符串的地方使用它。但是,通过将代码标记为 async
,我们现在已使其成为返回整数的异步函数,这意味着我们不能在需要返回整数的同步函数的地方使用它。
This is what I mean when I say that the async nature of the function is part of its type: it affects the way we refer to the function everywhere else in our code. This is exactly how throws
works – you can’t use a throwing function in a place that expects a non-throwing function.
这就是我所说的函数的异步性质是其类型的一部分的意思:它会影响我们在代码中其他任何地方引用函数的方式。这正是 throws
的工作原理 —— 你不能在需要非 throw 函数的地方使用 throwing 函数。
Second, notice that the work inside our function hasn’t actually changed. The same work is being done as before: this function doesn’t actually use the await
keyword at all, and that’s okay. You see, marking a function with async
means it might do asynchronous work, not that it must. Again, the same is true of throws
: some paths through a function might throw errors, but others might not.
其次,请注意,我们函数中的工作实际上并没有改变。正在完成与以前相同的工作:此函数实际上根本不使用 await
关键字,这没关系。你看,用 async
标记一个函数意味着它可能会做异步工作,而不是它必须做。同样,throw
也是如此:通过函数的某些路径可能会引发错误,但其他路径可能不会。
A third key difference arises when we call randomD6()
, because we need to do so asynchronously. Swift provides a few ways we can do this, but in our example we used await
, which means “run this function asynchronously and wait for its result to come back before continuing.”
当我们调用randomD6()
时,出现了第三个关键差异,因为我们需要异步执行此作。Swift 提供了几种方法可以做到这一点,但在我们的示例中,我们使用了 await
,这意味着 “异步运行此函数并等待其结果返回后再继续”。
So, what’s the actual difference between synchronous and asynchronous functions? To answer that, I want to show you a real function that does some async work to fetch a file from a web server:
那么,同步函数和异步函数之间的实际区别是什么呢?为了回答这个问题,我想向你展示一个真正的函数,它执行一些异步工作以从 Web 服务器获取文件:
func fetchNews() async -> Data? {
do {
let url = URL(string: "https://hws.dev/news-1.json")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
} catch {
print("Failed to fetch data")
return nil
}
}
if let data = await fetchNews() {
print("Downloaded \(data.count) bytes")
} else {
print("Download failed.")
}
Later on we’ll be digging in more to how that actually works, but for now what matters is that the URLSession.shared.data(from:)
method we call is asynchronous – its job is to fetch some data from a web server, without causing the whole program to freeze up.
稍后我们将更深入地研究它的实际工作原理,但现在重要的是我们调用的 URLSession.shared.data(from:)
方法是异步的——它的工作是从 Web 服务器获取一些数据,而不会导致整个程序冻结。
We’ve already seen that synchronous functions cause blocking, which leads to performance problems. Async functions do not block: when we call them with await
we are marking a suspension point, which is a place where the function can suspend itself – literally stop running – so that other work can happen. At some point in the future the function’s work completes, and Swift will wake it back up out of its “suspended animation”-like existence and it will carry on working.
我们已经看到,同步函数会导致阻塞,从而导致性能问题。异步函数不会阻塞:当我们使用 await
调用它们时,我们标记了一个暂停点,这是函数可以暂停自身的地方——字面意思是停止运行——以便其他工作可以发生。在未来的某个时刻,该函数的工作完成,Swift 将把它从类似 “暂停动画” 的存在中唤醒,并继续工作。
This might sound simple on paper, but in practice it’s very clever.
这在纸面上听起来很简单,但实际上却非常聪明。
First, when an async function is suspended, all the async functions that called it are also suspended; they all wait quietly while the async work happens, then resume later on. This is really important: async functions have this special ability to be suspended that regular synchronous functions do not. It’s for this reason that synchronous functions cannot call async functions directly – they don’t know how to suspend themselves.
首先,当异步函数被挂起时,调用它的所有异步函数也被挂起;他们都静静地等待异步工作发生,然后稍后继续。这真的很重要:异步函数具有常规同步函数所没有的暂停特殊能力。正是由于这个原因,同步函数不能直接调用异步函数——它们不知道如何挂起自己。
Second, a function can be suspended as many times as is needed, but it won’t happen without you writing await
there – functions won’t suspend themselves by surprise.
其次,一个函数可以根据需要多次暂停,但如果不在那里编写 await
就不会发生——函数不会意外地暂停自己。
Third, a function that is suspended does not block the thread it’s running on, and instead it gives up that thread so that Swift can do other work instead.
第三,一个被挂起的函数不会阻塞它正在运行的线程,而是放弃该线程,以便 Swift 可以做其他工作。
Although we can tell Swift how important many tasks are, we don’t get to decide exactly how the system schedules our work – it automatically takes care of all the threads working under the hood. This means if we call async function A without waiting for its result, then a moment later call async function B, it’s entirely possible B will start running before A does.
虽然我们可以告诉 Swift 有多少任务有多重要,但我们无法决定系统如何安排我们的工作 —— 它会自动处理所有在后台工作的线程。这意味着如果我们在不等待其结果的情况下调用异步函数 A,然后在片刻后调用异步函数 B,则 B 完全有可能在 A 之前开始运行。
Fourth, when the function resumes, it might be running on the same thread as before, but it might not; Swift gets to choose, and you shouldn’t make any assumptions here. This means by the time your function resumes all sorts of things might have changed in your program – a few milliseconds might have passed, or perhaps 20 seconds or more.
第四,当函数恢复时,它可能与以前在同一线程上运行,但也可能不是;Swift 可以自由选择,您不应该在这里做任何假设。这意味着当你的函数恢复时,你的程序中的各种事情可能已经发生了变化——可能已经过去了几毫秒,或者可能已经过去了 20 秒或更长时间。
And finally, I know I’m repeating myself, but this matters: just because a function is async doesn’t mean it will suspend – the await
keyword only marks a potential suspension point.
最后,我知道我在重复一遍,但这很重要:仅仅因为一个函数是异步的并不意味着它会挂起——await
关键字只标记一个潜在的挂起点。
Swift knows perfectly well that the function we’re calling is async, so this await
keyword is a marker for us humans rather than for the compiler – it’s a way of clearly marking which parts of the function might suspend, so you can know for sure which parts of the function run as one atomic chunk. (“Atomic” is a fancy word meaning “indivisible” – a chunk of work where all lines of code will execute without being interrupted by other code running.) This requirement for await
is identical to the requirement for try
, where we must mark each line of code that might throw errors.
Swift 非常清楚我们调用的函数是异步的,所以这个 await
关键字是我们人类的标记,而不是编译器的标记 —— 这是一种清楚地标记函数的哪些部分可能会挂起的方法,因此您可以确定函数的哪些部分作为一个原子块运行。(“原子”是一个花哨的词,意思是“不可分割的”——所有代码行都将执行而不会被其他正在运行的代码打断的工作块。对 await
的要求与 try
的要求相同,我们必须标记可能引发错误的每一行代码。
So, async functions are like regular functions, except they have a superpower: if they need to, they can suspend themselves and all their callers, freeing up their thread to do other work.
因此,异步函数与常规函数类似,不同之处在于它们具有超能力:如果需要,它们可以暂停自身和所有调用方,从而释放线程来执行其他工作。