swift - 如何将 DispatchQueue debounce 转换为 Swift 并发任务?
问题描述
我有一个使用DispatchQueue
. 它接受一个闭包并在达到时间阈值之前执行它。它可以这样使用:
let limiter = Debouncer(limit: 5)
var value = ""
func sendToServer() {
limiter.execute {
print("\(Date.now.timeIntervalSince1970): Fire! \(value)")
}
}
value.append("h")
sendToServer() // Waits until 5 seconds
value.append("e")
sendToServer() // Waits until 5 seconds
value.append("l")
sendToServer() // Waits until 5 seconds
value.append("l")
sendToServer() // Waits until 5 seconds
value.append("o")
sendToServer() // Waits until 5 seconds
print("\(Date.now.timeIntervalSince1970): Last operation called")
// 1635691696.482115: Last operation called
// 1635691701.859087: Fire! hello
请注意,它不是Fire!
多次调用,而是在最后一次使用最后一个任务的值后 5 秒。该Debouncer
实例被配置为将队列中的最后一个任务保持 5 秒,无论它被调用多少次。闭包被传递到execute(block:)
方法中:
final class Debouncer {
private let limit: TimeInterval
private let queue: DispatchQueue
private var workItem: DispatchWorkItem?
private let syncQueue = DispatchQueue(label: "Debouncer", attributes: [])
init(limit: TimeInterval, queue: DispatchQueue = .main) {
self.limit = limit
self.queue = queue
}
@objc func execute(block: @escaping () -> Void) {
syncQueue.async { [weak self] in
if let workItem = self?.workItem {
workItem.cancel()
self?.workItem = nil
}
guard let queue = self?.queue, let limit = self?.limit else { return }
let workItem = DispatchWorkItem(block: block)
queue.asyncAfter(deadline: .now() + limit, execute: workItem)
self?.workItem = workItem
}
}
}
如何将其转换为并发操作,以便可以如下调用:
let limit = Debouncer(limit: 5)
func sendToServer() {
await limiter.waitUntilFinished
print("\(Date.now.timeIntervalSince1970): Fire! \(value)")
}
sendToServer()
sendToServer()
sendToServer()
但是,这不会取消任务,而是暂停它们直到下一个被调用。相反,它应该取消前一个任务并保持当前任务直到去抖动时间。这可以用Swift Concurrency完成还是有更好的方法来做到这一点?
解决方案
任务有能力使用isCancelled
or checkCancellation
,但是为了去抖动例程,你想等待一段时间,你可能只使用 的 throwing rendition Task.sleep(nanoseconds:)
,其文档说:
如果任务在时间结束前被取消,这个函数会抛出
CancellationError
.
因此,这有效地消除了 2 秒的抖动。
var task: Task<(), Never>?
func debounced(_ string: String) {
task?.cancel()
task = Task {
do {
try await Task.sleep(nanoseconds: 2_000_000_000)
logger.log("result \(string)")
} catch {
logger.log("canceled \(string)")
}
}
}
(为什么苹果恢复到纳秒是我无法理解的。)
注意,non-throwing 的渲染sleep(nanoseconds:)
不会检测到取消,所以你必须使用这个 throwing 的渲染。
推荐阅读
- html - Ul 标签未在引导卡上占据全宽
- pandas - 在包含问题的数据框列中查找行
- docker - Jenkins:dockerfile 代理命令不在容器中运行
- python - 比较特定索引处的 2 个列表列表,Python
- batch-file - 卸载批处理文件不起作用关闭
- c# - 为什么 try catch 异常会在 SSIS 中错误地报告文件路径?
- angular - 在 MatSnackBar 中使用“snackBar.openFromComponent()”方法时如何放置动作按钮?
- ios - 我如何与 Metal 沟通我正在避免 GPU 和 CPU 之间的数据冲突
- grammar - T-SQL 语法分析器规则与自身相互左递归
- reactjs - 如何在 React 中使用多个不间断空格?