How to discard results in a task group 如何丢弃任务组中的结果

How to discard results in a task group 如何丢弃任务组中的结果

温馨提示:本文最后更新于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)
        }
    }
}
下载图标
how-to-discard-results-in-a-task-group-1.zip
zip文件
50.1K

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)
        }
    }
}
下载图标
how-to-discard-results-in-a-task-group-2.zip
zip文件
50.1K

Discarding task groups also have a throwing discarding task group, made using withThrowingDiscardingTaskGroup().
丢弃任务组也有一个抛出丢弃任务组,该任务组使用 withThrowingDiscardingTaskGroup() .

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享