我正在执行搜索。每个搜索查询都会生成一个DispatchWorkItem,然后将其排队等待执行。由于用户可以比上一次完成更快地触发新搜索,因此我想在收到新搜索后立即取消上一个搜索。
这是我当前的设置:
var currentSearchJob: DispatchWorkItem?
let searchJobQueue = DispatchQueue(label: QUEUE_KEY)
func updateSearchResults(for searchController: UISearchController) {
let queryString = searchController.searchBar.text?.lowercased() ?? ""
// if there is already an (older) search job running, cancel it
currentSearchJob?.cancel()
// create a new search job
currentSearchJob = DispatchWorkItem() {
self.filter(queryString: queryString)
}
// start the new job
searchJobQueue.async(execute: currentSearchJob!)
}
我知道dispatchWorkItem.cancel()
不会立即终止正在运行的任务。相反,我需要手动检查dispatchWorkItem.isCancelled
。但是在这种情况下如何获得正确的dispatchWorkItem
对象?
如果只设置一次currentSearchJob
,我可以像完成in this case那样简单地访问该属性。但是,这在这里不适用,因为在完成filter()
方法之前,该属性将被覆盖。 我如何知道哪个实例实际在运行我要检查dispatchWorkItem.isCancelled
的代码?
理想情况下,我想提供新创建的DispatchWorkItem
作为filter()
方法的附加参数。但这是不可能的,因为我会遇到Variable used within its own initial value
错误。
我是Swift的新手,所以我希望我只是想念一些东西。非常感谢任何帮助!
答案 0 :(得分:1)
诀窍是如何检查已取消的调度任务。我实际上建议您考虑使用OperationQueue
方法,而不是直接使用调度队列。
至少有两种方法:
最优雅的恕我直言,只是对Operation
进行子类化,在init
方法中传递您想要的任何内容,并在main
方法中执行工作:< / p>
class SearchOperation: Operation {
private var queryString: String
init(queryString: Int) {
self.queryString = queryString
super.init()
}
override func main() {
// do something synchronous, periodically checking `isCancelled`
// e.g., for illustrative purposes
print("starting \(queryString)")
for i in 0 ... 10 {
if isCancelled { print("canceled \(queryString)"); return }
print(" \(queryString): \(i)")
heavyWork()
}
print("finished \(queryString)")
}
func heavyWork() {
Thread.sleep(forTimeInterval: 0.5)
}
}
因为它在Operation
子类中,所以isCancelled
隐式引用了自己而不是某些ivar,从而避免了对其检查内容的混淆。您的“开始新查询”代码只能说“取消相关操作队列上当前的任何内容,然后在该队列上添加新操作”:
private var searchQueue: OperationQueue = {
let queue = OperationQueue()
// queue.maxConcurrentOperationCount = 1 // make it serial if you want
queue.name = Bundle.main.bundleIdentifier! + ".backgroundQueue"
return queue
}()
func performSearch(for queryString: String) {
searchQueue.cancelAllOperations()
let operation = SearchOperation(queryString: queryString)
searchQueue.addOperation(operation)
}
我建议您采用这种方法,因为最终会产生一个小的内聚对象,即操作,它按照“单一责任原则”的精神很好地封装了您要完成的工作。
虽然以下内容不太美观,但从技术上讲,您也可以使用BlockOperation
,它是基于块的,但是您可以将其分离以创建操作以及将闭包添加到操作。使用这种技术,您实际上可以将对该操作的引用传递给它自己的闭包:
private weak var lastOperation: Operation?
func performSearch(for queryString: String) {
lastOperation?.cancel()
let operation = BlockOperation()
operation.addExecutionBlock { [weak operation, weak self] in
print("starting \(identifier)")
for i in 0 ... 10 {
if operation?.isCancelled ?? true { print("canceled \(identifier)"); return }
print(" \(identifier): \(i)")
self?.heavyWork()
}
print("finished \(identifier)")
}
searchQueue.addOperation(operation)
lastOperation = operation
}
func heavyWork() {
Thread.sleep(forTimeInterval: 0.5)
}
为了完整起见,我仅提及这一点。我认为Operation
子类方法通常是一种更好的设计。我将使用BlockOperation
处理一次性内容,但是一旦我想要更复杂的取消逻辑,我认为Operation
子类方法会更好。
我还应该提到,Operation
对象除了具有更优雅的取消功能外,还提供各种其他复杂的功能(例如,异步管理本身是异步的任务队列;限制并发程度;等等。 )。这超出了这个问题的范围。
答案 1 :(得分:-1)
您写了
理想情况下,我想将新创建的DispatchWorkItem作为 附加参数
您错了,要取消正在运行的任务,您需要引用它,而不是要准备下一个任务的引用。
cancel()不会取消正在运行的任务,它仅以线程安全的方式设置内部“ isCancel”标志,或者在执行之前将其从队列中删除。执行后,检查isCancel可以使您有机会完成工作(尽早返回)。
import PlaygroundSupport
import Foundation
PlaygroundPage.current.needsIndefiniteExecution = true
let queue = DispatchQueue.global(qos: .background)
let prq = DispatchQueue(label: "print.queue")
var task: DispatchWorkItem?
func work(task: DispatchWorkItem?) {
sleep(1)
var d = Date()
if task?.isCancelled ?? true {
prq.async {
print("cancelled", d)
}
return
}
sleep(3)
d = Date()
prq.async {
print("finished", d)
}
}
for _ in 0..<3 {
task?.cancel()
let item = DispatchWorkItem {
work(task: task)
}
item.notify(queue: prq) {
print("done")
}
queue.asyncAfter(deadline: .now() + 0.5, execute: item)
task = item
sleep(1) // comment this line
}
在此示例中,只有最后一项工作才真正被完全执行
cancelled 2018-12-17 23:49:13 +0000
done
cancelled 2018-12-17 23:49:14 +0000
done
finished 2018-12-17 23:49:18 +0000
done
尝试注释最后一行并打印出来
done
done
finished 2018-12-18 00:07:28 +0000
done
区别在于,前两次执行从未发生。 (在执行前已从调度队列中删除)