2

イムでの操作を実行してキャンセルすることはできませんグラフデータを再計算する必要があります。ユーザーがチャートデータフィルタリングボタンを非常に素早くクリックすると(パワーユーザー)、GCDディスパッチの非同期が終了するたびにグラフがループします。は、私はもともとGCDを使いましたが、毎回ユーザーがボタンを押すことでグラフのデータをフィルタリングし、バックグラウンドスレッド</p> <p>上のチャートデータを作成するために、いくつかの長い計算をやってOperationQueue迅速

GCDでスレッドをキャンセルできませんだから実装しようとしましたOperationQueue

私は0を呼び出しますキューに新しい操作を追加する前に

キューの操作はファンキーな動作をしますが、キャンセルされたように見えることもありますが、キューに入れられた最新のものではない場合があります。

私も、私は事業完了ブロックでそれを確認したときの動作の.isCancelledプロパティがtrueになることはありませんとして実行する操作をキャンセルするトラブルを抱えています

チャートデータの計算は、現在で起こっている場合は私が本当に欲しいのですバックグラウンドスレッドで、別のフィルタボタンをクリックしてバックグラウンドスレッドで別のチャート計算を開始すると、直前のチャートバックグラウンド計算が終了し、直近の追加操作で「置き換えられました」

これは可能ですか?さておき、それは完全に真実ではないのと同じように

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) 
} 

enter image description here

+1

操作をキャンセルすると、競合状態が発生する可能性があります。回避することはできません。 GUIまたはその他のローカルでない状態変数を更新する前に、操作の始めにすべての時間のかかる呼び出しをスケジュールすることは有益です。あなたの操作で最も時間のかかる機能は何ですか? –

+1

また、WWDC 2015から[Advanced NSOperations](https://developer.apple.com/videos/play/wwdc2015/226/?time=307)をお勧めします。取り消しについては、5:00のマーク –

+0

で議論されています。ユーザーがボタンをクリックするたびにこの関数が呼び出されます。ユーザーがボタンを3回すばやくクリックしないようにしてから、チャートをゆっくり3回更新すると、ユーザーには遅く感じられます。 –

答えて

1

私は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() 
} 

が、それは仕事をして:あなたはGCDキューにディスパッチ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サブクラスを作成します。OperationOperationQueueの利点の1つは、複雑な操作コードをビューコントローラコードから解き放つことができるということです。例えば

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) 

そして、この:

とにかく、あなたのビューコントローラのような何かを行うことができます。ところで


、私はあなたにも、あなたの様々なforループ内部isCancelledをチェックしていることを確認(または多分ちょうど最も深くネストされたforループにしたいことがあり、操作が解約であることを確認する方法を示しながら、 )。そのままで、プロセスの早い段階でisCancelledをチェックしています。後でチェックしないと、その後の取り消しは無視されます。ディスパッチおよびオペレーションキューはプリエンプティブキャンセルを実行しないため、取り消しを認識させたい箇所にisCancelledチェックを挿入する必要があります。

関連する問題

 関連する問題