【发布时间】:2017-07-28 20:28:50
【问题描述】:
我正在做一些冗长的计算以在后台线程上创建图表数据
我最初是使用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)
}
【问题讨论】:
-
取消操作容易受到竞争条件的影响,您无法避免这种情况。在更新 GUI 或任何非本地状态变量之前,在操作开始时安排所有耗时的调用可能对您很有帮助。您的操作中最耗时的功能是什么?
-
我还推荐 WWDC 2015 的 Advanced NSOperations。在 5:00 左右讨论取消
-
问题是每次用户点击按钮都会调用整个函数,我想避免用户快速点击按钮三次,然后图表缓慢更新三次,似乎这样做时对用户很迟钝
-
然后在更新进行时禁用刷新按钮更有意义,不是吗?
-
我添加了一个屏幕截图,你可以看到用户可以点击一个按钮来添加或删除图表上的数据点,它是发生在 .utility 队列上的图表数据计算。您真的认为在后台线程上开始计算图表数据时禁用数据过滤按钮是最好的用户体验吗?这是常见的 iOS 用户体验实践吗?
标签: ios swift multithreading grand-central-dispatch nsoperationqueue