admin管理员组文章数量:1035883
Swift 并发中的任务让步(Yielding)和防抖(Debouncing)
前言
本篇文章的主题是 任务让步(Task Yielding) 和 防抖(Debouncing)。Swift 并发为我们提供了两个简单但非常强大的函数:yield
和 sleep
。今天我们就来看看它们的用法,以及在什么场景下应该使用它们。
什么是任务防抖(Debouncing)?
想象一下,你正在开发一个搜索功能,用户每输入一个字符,程序就会去一个庞大的数据集里查找匹配的结果。如果不加控制,每次键入都会触发新的搜索任务,可能会导致多个任务同时执行,影响性能甚至引发竞争条件(Race Condition)。
比如,下面这个 SwiftUI 代码:
代码语言:swift复制@MainActor @Observable final class Store {
private(set) var results: [HKCorrelation] = []
private let store = HKHealthStore()
func search(matching query: String) async {
// 执行复杂的搜索任务
}
}
struct ContentView: View {
@State private var store = Store()
@State private var query = ""
var body: some View {
NavigationStack {
List(store.results, id: \.uuid) { result in
Text(verbatim: result.endDate.formatted())
}
.searchable(text: $query)
.task(id: query) {
await store.search(matching: query)
}
}
}
}
在这个代码里,每次用户输入新字符,都会创建一个新的任务去执行搜索。而 Swift 并发采用 协作式取消机制(Cooperative Cancellation),也就是说,它不会直接强行终止任务,而是提供一个“取消标记”,任务需要自己检查并响应取消请求。因此,这种写法可能会导致多个搜索任务并行运行,消耗不必要的计算资源。
为了解决这个问题,我们可以用 防抖(Debouncing) 技术。
如何用 sleep 实现防抖?
防抖的思路很简单:
- 用户输入时,我们 等待一小段时间,看看用户是否继续输入。
- 如果输入仍然在变化,我们就 继续等待,而不是立即启动搜索。
- 只有当输入 稳定 一定时间后,才触发搜索任务。
换句话说,如果用户输入 "apple"
,我们希望忽略 "a"
, "ap"
, "app"
, "appl"
,只在最终输入 "apple"
后再进行搜索。
在 Swift 并发中,我们可以用 Task.sleep
来实现这个效果:
struct ContentView: View {
@State private var store = Store()
@State private var query = ""
var body: some View {
NavigationStack {
List(store.results, id: \.uuid) { result in
Text(verbatim: result.endDate.formatted())
}
.searchable(text: $query)
.task(id: query) {
do {
try await Task.sleep(for: .seconds(1)) // 等待 1 秒
await store.search(matching: query) // 执行搜索
} catch {
// 任务可能因为新输入被取消
}
}
}
}
}
为什么这样就能实现防抖?
Task.sleep(for: .seconds(1))
让当前任务暂停 1 秒。- 如果用户在 1 秒内继续输入,之前的任务会被取消,新任务重新计时。
- 只有 用户停止输入超过 1 秒,才会触发真正的搜索任务。
效果: 这样可以避免在输入过程中反复触发搜索,减少不必要的计算量。
什么是任务让步(Task Yielding)?
除了防抖,Swift 并发还提供了 任务让步(Task Yielding),让你在执行长时间任务时,主动把线程让出来,让其他任务有机会运行。
想象一个场景:
你需要解析一批巨大的 JSON 文件,并将数据保存到磁盘。这个过程可能会运行很久,占用线程资源。如果你在主线程或并发线程池(Cooperative Thread Pool)上运行这种任务,会 阻塞其他任务的执行,导致性能问题。
比如,下面是一个解析 JSON 文件的代码:
代码语言:swift复制struct Item: Decodable {
// 解析 JSON 的结构
}
struct DataHandler {
func process(json files: [Data]) async throws -> [Item] {
let decoder = JSONDecoder()
var result: [Item] = []
for file in files {
let items = try decoder.decode([Item].self, from: file)
result.append(contentsOf: items)
}
return result
}
}
这个 process
函数会遍历所有 JSON 文件,并解析它们。但问题是:
- 解析 JSON 是一个 同步操作,它不会自动释放 CPU 资源。
- 如果 JSON 文件很大,整个解析过程可能会 占用线程很长时间,导致其他任务被阻塞。
如何用 yield 让出线程?
为了解决这个问题,我们可以 在每次解析完一个 JSON 文件后,让出线程,让其他任务有机会执行:
代码语言:swift复制struct DataHandler {
func process(json files: [Data]) async throws -> [Item] {
let decoder = JSONDecoder()
var result: [Item] = []
for file in files {
let items = try decoder.decode([Item].self, from: file)
result.append(contentsOf: items)
await Task.yield() // 让出线程,让其他任务有机会执行
}
return result
}
}
任务让步的好处:
await Task.yield()
会让当前任务 暂停一下,让其他等待中的任务有机会执行。- 之后,系统会恢复这个任务的执行,继续处理下一个 JSON 文件。
- 这样可以 更公平地分配 CPU 资源,防止某个任务独占线程。
什么时候需要 yield?
通常来说,如果你的代码已经是 异步的(async/await),系统会自动在 await
语句处让出线程。所以 大部分情况下,你不需要手动 yield
。
但是,当你处理 非异步 API(比如 JSON 解析、图片处理、大量计算等)时,手动 yield
可能会提升性能。
总结
- 防抖(Debouncing)
- 适用于 用户频繁输入的场景,如搜索框、按钮点击等。
- 通过
Task.sleep(for:)
实现,等输入稳定后再执行任务。 - 避免频繁创建任务,提高性能。
- 任务让步(Task Yielding)
- 适用于 长时间运行的计算密集型任务,如解析 JSON、图片处理等。
- 通过
Task.yield()
让出 CPU,避免线程被长时间占用。 - 让其他任务有机会执行,提高系统响应速度。
这两个技巧虽然简单,但在实际开发中非常有用,可以帮助你更高效地利用 Swift 并发,让你的应用运行得更流畅!
Swift 并发中的任务让步(Yielding)和防抖(Debouncing)
前言
本篇文章的主题是 任务让步(Task Yielding) 和 防抖(Debouncing)。Swift 并发为我们提供了两个简单但非常强大的函数:yield
和 sleep
。今天我们就来看看它们的用法,以及在什么场景下应该使用它们。
什么是任务防抖(Debouncing)?
想象一下,你正在开发一个搜索功能,用户每输入一个字符,程序就会去一个庞大的数据集里查找匹配的结果。如果不加控制,每次键入都会触发新的搜索任务,可能会导致多个任务同时执行,影响性能甚至引发竞争条件(Race Condition)。
比如,下面这个 SwiftUI 代码:
代码语言:swift复制@MainActor @Observable final class Store {
private(set) var results: [HKCorrelation] = []
private let store = HKHealthStore()
func search(matching query: String) async {
// 执行复杂的搜索任务
}
}
struct ContentView: View {
@State private var store = Store()
@State private var query = ""
var body: some View {
NavigationStack {
List(store.results, id: \.uuid) { result in
Text(verbatim: result.endDate.formatted())
}
.searchable(text: $query)
.task(id: query) {
await store.search(matching: query)
}
}
}
}
在这个代码里,每次用户输入新字符,都会创建一个新的任务去执行搜索。而 Swift 并发采用 协作式取消机制(Cooperative Cancellation),也就是说,它不会直接强行终止任务,而是提供一个“取消标记”,任务需要自己检查并响应取消请求。因此,这种写法可能会导致多个搜索任务并行运行,消耗不必要的计算资源。
为了解决这个问题,我们可以用 防抖(Debouncing) 技术。
如何用 sleep 实现防抖?
防抖的思路很简单:
- 用户输入时,我们 等待一小段时间,看看用户是否继续输入。
- 如果输入仍然在变化,我们就 继续等待,而不是立即启动搜索。
- 只有当输入 稳定 一定时间后,才触发搜索任务。
换句话说,如果用户输入 "apple"
,我们希望忽略 "a"
, "ap"
, "app"
, "appl"
,只在最终输入 "apple"
后再进行搜索。
在 Swift 并发中,我们可以用 Task.sleep
来实现这个效果:
struct ContentView: View {
@State private var store = Store()
@State private var query = ""
var body: some View {
NavigationStack {
List(store.results, id: \.uuid) { result in
Text(verbatim: result.endDate.formatted())
}
.searchable(text: $query)
.task(id: query) {
do {
try await Task.sleep(for: .seconds(1)) // 等待 1 秒
await store.search(matching: query) // 执行搜索
} catch {
// 任务可能因为新输入被取消
}
}
}
}
}
为什么这样就能实现防抖?
Task.sleep(for: .seconds(1))
让当前任务暂停 1 秒。- 如果用户在 1 秒内继续输入,之前的任务会被取消,新任务重新计时。
- 只有 用户停止输入超过 1 秒,才会触发真正的搜索任务。
效果: 这样可以避免在输入过程中反复触发搜索,减少不必要的计算量。
什么是任务让步(Task Yielding)?
除了防抖,Swift 并发还提供了 任务让步(Task Yielding),让你在执行长时间任务时,主动把线程让出来,让其他任务有机会运行。
想象一个场景:
你需要解析一批巨大的 JSON 文件,并将数据保存到磁盘。这个过程可能会运行很久,占用线程资源。如果你在主线程或并发线程池(Cooperative Thread Pool)上运行这种任务,会 阻塞其他任务的执行,导致性能问题。
比如,下面是一个解析 JSON 文件的代码:
代码语言:swift复制struct Item: Decodable {
// 解析 JSON 的结构
}
struct DataHandler {
func process(json files: [Data]) async throws -> [Item] {
let decoder = JSONDecoder()
var result: [Item] = []
for file in files {
let items = try decoder.decode([Item].self, from: file)
result.append(contentsOf: items)
}
return result
}
}
这个 process
函数会遍历所有 JSON 文件,并解析它们。但问题是:
- 解析 JSON 是一个 同步操作,它不会自动释放 CPU 资源。
- 如果 JSON 文件很大,整个解析过程可能会 占用线程很长时间,导致其他任务被阻塞。
如何用 yield 让出线程?
为了解决这个问题,我们可以 在每次解析完一个 JSON 文件后,让出线程,让其他任务有机会执行:
代码语言:swift复制struct DataHandler {
func process(json files: [Data]) async throws -> [Item] {
let decoder = JSONDecoder()
var result: [Item] = []
for file in files {
let items = try decoder.decode([Item].self, from: file)
result.append(contentsOf: items)
await Task.yield() // 让出线程,让其他任务有机会执行
}
return result
}
}
任务让步的好处:
await Task.yield()
会让当前任务 暂停一下,让其他等待中的任务有机会执行。- 之后,系统会恢复这个任务的执行,继续处理下一个 JSON 文件。
- 这样可以 更公平地分配 CPU 资源,防止某个任务独占线程。
什么时候需要 yield?
通常来说,如果你的代码已经是 异步的(async/await),系统会自动在 await
语句处让出线程。所以 大部分情况下,你不需要手动 yield
。
但是,当你处理 非异步 API(比如 JSON 解析、图片处理、大量计算等)时,手动 yield
可能会提升性能。
总结
- 防抖(Debouncing)
- 适用于 用户频繁输入的场景,如搜索框、按钮点击等。
- 通过
Task.sleep(for:)
实现,等输入稳定后再执行任务。 - 避免频繁创建任务,提高性能。
- 任务让步(Task Yielding)
- 适用于 长时间运行的计算密集型任务,如解析 JSON、图片处理等。
- 通过
Task.yield()
让出 CPU,避免线程被长时间占用。 - 让其他任务有机会执行,提高系统响应速度。
这两个技巧虽然简单,但在实际开发中非常有用,可以帮助你更高效地利用 Swift 并发,让你的应用运行得更流畅!
本文标签: Swift 并发中的任务让步(Yielding)和防抖(Debouncing)
版权声明:本文标题:Swift 并发中的任务让步(Yielding)和防抖(Debouncing) 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/jiaocheng/1748215982a2270622.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论