我正在做一些冗长的计算,以在后台线程上创建图表数据
我最初使用GCD,但每次用户通过点击按钮过滤图表数据时,如果用户非常快速地点击图表数据过滤按钮(高级用户),则需要重新计算图表数据,然后图表循环通过每个绘图,每个GCD调度异步完成
我意识到我无法使用GCD取消线程,所以我已经开始尝试实现OperationQueue
我在向队列添加新操作之前调用cancelAllOperations()
队列上的操作很时髦,有时似乎它们被取消了,有时似乎完成的那个并不是最新的队列。
我也无法取消正在执行的操作,因为当我在操作完成块中检查它时,操作的.isCancelled属性永远不会为真
我真正想要的是,如果图表数据计算当前正在后台线程中进行,并且用户单击另一个过滤器按钮并在后台线程上启动另一个图表计算,则先前的图表后台线程计算将终止并“替换” “使用最近添加的操作
这可能吗? 这是一些代码:func setHistoricalChart() -> Void {
self.lineChartView.clear()
self.lineChartView.noDataText = "Calculating Historical Totals, Please Wait..."
self.historicalOperationsQueue.qualityOfService = .utility
self.historicalOperationsQueue.maxConcurrentOperationCount = 1
self.historicalOperationsQueue.name = "historical operations queue"
let historicalOperation = Operation()
historicalOperation.completionBlock = { [weak self] in
//dictionary of feeds, array of data for each feed
var valuesByFeed = [String:[String]?]()
var dates = [String:[String]?]()
var chartDataSets = [IChartDataSet]()
//get data and values from DataMOs in the activeFeeds
if (self?.activeFeeds.count)! > 0 {
//check if operation is cancelled
if historicalOperation.isCancelled {
return
}
for (key, feed) in (self?.activeFeeds)! {
dates[key] = feed?.datas?.flatMap({ Utils.formatUTCDateString(utcDateString: ($0 as! DataMO).utcDateString) })
valuesByFeed[key] = feed?.datas?
.sorted(by: { (($0 as! DataMO).utcDateString)! < (($1 as! DataMO).utcDateString)! })
.flatMap({ ($0 as! DataMO).value })
}
//Create Chart Data
for (key, valuesArray) in valuesByFeed {
var dataEntries = [ChartDataEntry]()
for (index, value) in (valuesArray?.enumerated())! {
let dataEntry = ChartDataEntry(x: Double(index), y: Double(value)!)
dataEntries.append(dataEntry)
}
let singleChartDataSet = LineChartDataSet(values: dataEntries, label: key)
singleChartDataSet.drawCirclesEnabled = false
switch key {
case "Solar":
singleChartDataSet.setColors(UIColor(red: 230/255, green: 168/255, blue: 46/255, alpha: 1))
singleChartDataSet.drawFilledEnabled = true
singleChartDataSet.fillColor = UIColor(red: 230/255, green: 168/255, blue: 46/255, alpha: 0.8)
break
case "Wind":
singleChartDataSet.setColors(UIColor(red: 73/255, green: 144/255, blue: 226/255, alpha: 1))
singleChartDataSet.drawFilledEnabled = true
singleChartDataSet.fillColor = UIColor(red: 73/255, green: 144/255, blue: 226/255, alpha: 0.8)
break
case "Battery":
singleChartDataSet.setColors(UIColor(red: 126/255, green: 211/255, blue: 33/255, alpha: 1))
singleChartDataSet.drawFilledEnabled = true
singleChartDataSet.fillColor = UIColor(red: 126/255, green: 211/255, blue: 33/255, alpha: 0.8)
break
case "Gen":
singleChartDataSet.setColors(UIColor(red: 208/255, green: 1/255, blue: 27/255, alpha: 1))
singleChartDataSet.drawFilledEnabled = true
singleChartDataSet.fillColor = UIColor(red: 208/255, green: 1/255, blue: 27/255, alpha: 0.8)
break
case "Demand":
singleChartDataSet.setColors(UIColor(red: 128/255, green: 133/255, blue: 233/255, alpha: 1))
singleChartDataSet.drawFilledEnabled = true
singleChartDataSet.fillColor = UIColor(red: 128/255, green: 133/255, blue: 233/255, alpha: 0.8)
break
case "Prod":
singleChartDataSet.setColors(UIColor(red: 241/255, green: 92/255, blue: 128/255, alpha: 1))
singleChartDataSet.drawFilledEnabled = true
singleChartDataSet.fillColor = UIColor(red: 241/255, green: 92/255, blue: 128/255, alpha: 0.8)
break
default:
break
}
chartDataSets.append(singleChartDataSet)
}
}
//check if operation is cancelled
if historicalOperation.isCancelled {
return
}
//set chart data
let chartData = LineChartData(dataSets: chartDataSets)
//update UI on MainThread
OperationQueue.main.addOperation({
if (self?.activeFeeds.count)! > 0 {
self?.lineChartView.data = chartData
} else {
self?.lineChartView.clear()
self?.lineChartView.noDataText = "No Feeds To Show"
}
})
}
historicalOperationsQueue.cancelAllOperations()
historicalOperationsQueue.addOperation(historicalOperation)
}
答案 0 :(得分:7)
我意识到我无法用GCD取消线程......
除此之外,这并非完全正确。您可以取消分派到GCD队列的DispatchWorkItem
个项目:
var item: DispatchWorkItem!
item = DispatchWorkItem {
...
while notYetDone() {
if item.isCancelled {
os_log("canceled")
return
}
...
}
os_log("finished")
}
let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".customQueue")
queue.async(execute: item)
// just to prove it's cancelable, let's cancel it one second later
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
os_log("canceling")
item.cancel()
}
不可否认,您必须取消单个DispatchWorkItem
个实例,但它确实有效。
...所以我已经开始尝试实施
了OperationQueue
不幸的是,这还没有正确实施。简而言之,您的问题中的代码是创建一个在操作本身中不执行任何操作的操作,而是在其完成处理程序中具有所有计算密集型代码。但是只有在操作“完成”后才会调用此完成处理程序。已完成的操作(即已经运行完成处理程序的操作)无法取消。因此,操作将忽略取消这些正在进行的,耗时的完成处理程序块的尝试。
相反,创建一个块操作,并将您的逻辑添加为“执行块”,而不是完成处理程序。然后取消按预期工作:
let operation = BlockOperation()
operation.addExecutionBlock {
...
while notYetDone() {
if operation.isCancelled {
os_log("canceled")
return
}
...
}
os_log("finished")
}
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.addOperation(operation)
// just to prove it's cancelable, let's cancel it
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
os_log("canceling")
operation.cancel()
}
或者,甚至可能更好,创建一个Operation
子类来完成这项工作。 Operation
和OperationQueue
的一个优点是,您可以从视图控制器代码中解开复杂的操作代码。
例如:
class ChartOperation: Operation {
var feeds: [Feed]
private var chartOperationCompletion: (([IChartDataSet]?) -> Void)?
init(feeds: [Feed], completion: (([IChartDataSet]?) -> Void)? = nil) {
self.feeds = feeds
self.chartOperationCompletion = completion
super.init()
}
override func main() {
let results = [IChartDataSet]()
while notYetDone() {
if isCancelled {
OperationQueue.main.addOperation {
self.chartOperationCompletion?(nil)
self.chartOperationCompletion = nil
}
return
}
...
}
OperationQueue.main.addOperation {
self.chartOperationCompletion?(results)
self.chartOperationCompletion = nil
}
}
}
我不知道您的activeFeeds
是什么,所以我将其声明为Feed
的数组,但会根据您的需要进行调整。但它说明了同步操作的想法:只需子类Operation
并添加main
方法。如果要将数据传递给操作,请将其作为参数添加到init
方法。如果要传回数据,请添加一个闭包参数,该参数将在操作完成时调用。注意,我更喜欢这依赖于内置的completionHandler
,因为它没有提供像上面的自定义完成处理程序那样提供传递给闭包的参数的机会。
无论如何,您的视图控制器可以执行以下操作:
let operation = ChartOperation(feeds: activeFeeds) { results in
// update UI here
}
queue.addOperation(operation)
这和上面的例子一样,可以取消。
顺便说一句,虽然我展示了如何确保操作可取消,但您可能还需要确保在各种isCancelled
循环中检查for
(或者最多深度嵌套的for
循环)。实际上,您在过程的早期检查isCancelled
,如果您以后不检查,它将忽略后续的取消。调度和操作队列不执行抢先取消,因此您必须在要识别取消的任何位置插入isCancelled
检查。