Search code examples
swiftgrand-central-dispatch

Swift GCD Work Item Dispatch queue cancelling


I have actions that must continue until the switch that initiated them is changed. I have tried to use GCD with a Work Item queue believing that the async action would allow the user to change the switch and consequently stop the actions. When the code runs it never sees or even triggers a switch change. Here is my code. Any idea what I am doing wrong?

@IBAction func trailerScanSwitchChange(_ sender: Any) {

    let model = Model()     //Instantiate the Model
    gScan = trailerScanSwitch.isOn
    //Set up work iten to read and parse data
    let work1 = DispatchWorkItem {
      while (gScan && gConnected){
        model.parseValues()
        print ("parsing values")
        if gScan == false || gConnected == false{
          break
        }
        model.convertVoltsToLights()
        self.updateLights()
        print ("updating Lights")
        if gScan == false || gConnected == false{
          break
        }
      }
    }
    if trailerScanSwitch.isOn == true{
      print ("Scanning")
      //perform on the current thread
      work1.perform()
      //perpform on the global queue
      DispatchQueue.global(qos: .background).async(execute: work1)  // concurrent actions
      return
    }
    else {     //Stop reading and displaying
      gScan = false
      work1.cancel()
    }
}

Solution

  • You need to declare DispatchWorkItem variable at the top of class. In your code work1 variable is becoming inaccessible as soon as compiler is out of function. Every time you change your switch a new DispatchWorkItem variable initialize. Please go through below example for correct use of DispatchWorkItem with stop/resume functionality

        @IBOutlet weak var button: UIButton!
        @IBOutlet weak var label: UILabel!
        var isStart = false
        var work: DispatchWorkItem!
    
        private func getCurrentTime() -> DispatchWorkItem {
    
            work = DispatchWorkItem { [weak self] in
    
                while true {
                    if (self?.work.isCancelled)!{break}
                    let date = Date()
                    let component = Calendar.current.dateComponents([.second], from: date)
                    DispatchQueue.main.async {
                        self?.label.text = "\(component.second!)"
                    }
    
                }
    
            }
    
            return work
        }
    
        @IBAction func btnPressed() {
    
            isStart = !isStart
            button.setTitle(isStart ? "Stop" : "Start", for: .normal)
    
            let workItem = getCurrentTime()
            let globalQueue = DispatchQueue.global(qos: .background)
            if isStart {
                globalQueue.async(execute: workItem)
            } else {
                workItem.cancel()
            }
    
        }
    

    This example will display current time seconds value in label. I have used while true, just to show an example.