2025-02-08 16:20:52
,某些文章具有时效性,若有错误或已失效,请在下方留言。Swift has a special task group type called a discarding task group, which has a very specialized function: tasks that complete are automatically discarded and destroyed, rather than us needing to wait for them manually by calling next()
or similar. This is particularly important for very long-running tasks, such as servers, where waiting for one task to complete is impractical.
Swift 有一个特殊的任务组类型,叫做 discarding task group,它有一个非常专业的功能:完成的任务会自动被丢弃和销毁,而我们不需要通过调用 next()
或类似命令来手动等待它们。这对于运行时间非常长的任务(如服务器)尤其重要,因为在这些任务中,等待一个任务完成是不切实际的。
We can try out a small example to let you see the problem. First, here’s a simple AsyncSequence
that continuously generates random numbers:
我们可以尝试一个小示例,让您看到问题所在。首先,这是一个持续生成随机数的简单 AsyncSequence
:
struct RandomGenerator: AsyncSequence, AsyncIteratorProtocol {
mutating func next() async -> Int? {
try? await Task.sleep(for: .seconds(0.001))
return Int.random(in: 1...Int.max)
}
func makeAsyncIterator() -> Self {
self
}
}
In your own code, that might be a server that continuously accepts connections, or a filesystem watcher that continuously scans for updates.
在您自己的代码中,这可能是持续接受连接的服务器,或者是持续扫描更新的文件系统观察器。
Now we could make some test code that waits for a new number to come in, and passes it to a task group child to be processed, like this:
现在我们可以制作一些测试代码,等待新数字进入,并将其传递给任务组子项进行处理,如下所示:
struct RandomGenerator: AsyncSequence, AsyncIteratorProtocol {
mutating func next() async -> Int? {
try? await Task.sleep(for: .seconds(0.001))
return Int.random(in: 1...Int.max)
}
func makeAsyncIterator() -> Self {
self
}
}
let generator = RandomGenerator()
await withTaskGroup(of: Void.self) { group in
for await newNumber in generator {
group.addTask {
print(newNumber)
}
}
}
Here we’re just printing the number, but if this were a server listening for new connections then the task we added would respond to those connections by serving up data.
在这里,我们只是打印数字,但如果这是一个侦听新连接的服务器,那么我们添加的任务将通过提供数据来响应这些连接。
Notice how we’re saying our task group child type is Void.self
, which means we’re explicitly saying these tasks will return no values. However, our code never actually waits for tasks to complete, so they sit there, building up slowly – if you run that code back you’ll it leaks about 0.5MB memory a second.
请注意,我们是如何将任务组子类型称为 Void.self
的,这意味着我们明确表示这些任务不会返回任何值。然而,我们的代码实际上从来没有等待任务完成,所以它们就坐在那里,慢慢地积累起来——如果你把这些代码运行回去,它每秒会泄漏大约 0.5MB 的内存。
Tip: I added a tiny delay in our generator to avoid your computer being overwhelmed. I would recommend against removing it!
提示: 我在生成器中添加了一个微小的延迟,以避免您的计算机不堪重负。我建议不要删除它!
To fix this, we would need to await the result of the child tasks – when the data for a particular connection has been fully sent, for example, that task can then be destroyed.
要解决此问题,我们需要等待子任务的结果 – 例如,当特定连接的数据已完全发送时,该任务可以被销毁。
However, that creates a different problem: if all the connections are currently being processed (i.e., if their tasks are currently active), then waiting for a task to finish will take some time, during which no new connections will be accepted.
但是,这会产生一个不同的问题:如果当前正在处理所有连接(即,如果它们的任务当前处于活动状态),则等待任务完成将需要一些时间,在此期间不会接受新的连接。
This is where discarding task groups come in: you never need to wait for their result, and in fact can’t wait for their result, because they are designed to be automatically destroyed on completion.
这就是丢弃任务组的用武之地:您永远不需要等待它们的结果,实际上也不能 等待它们的结果,因为它们被设计为在完成时自动销毁。
So, in our sample code we just need to replace this code:
因此,在我们的示例代码中,我们只需要替换以下代码:
await withTaskGroup(of: Void.self) { group in
With this code: 使用此代码:
await withDiscardingTaskGroup { group in
And now our code won’t leak memory any more, because old tasks will automatically be destroyed. Here’s how the full code looks:
现在我们的代码不会再泄漏内存,因为旧任务会自动销毁。以下是完整代码的样子:
struct RandomGenerator: AsyncSequence, AsyncIteratorProtocol {
mutating func next() async -> Int? {
try? await Task.sleep(for: .seconds(0.001))
return Int.random(in: 1...Int.max)
}
func makeAsyncIterator() -> Self {
self
}
}
let generator = RandomGenerator()
await withDiscardingTaskGroup { group in
for await newNumber in generator {
group.addTask {
print(newNumber)
}
}
}
Discarding task groups also have a throwing discarding task group, made using withThrowingDiscardingTaskGroup()
.
丢弃任务组也有一个抛出丢弃任务组,该任务组使用 withThrowingDiscardingTaskGroup()
.