0

I want to know if it is possible to suspend and then resume a work item on the main queue whilst maintaining the '.asyncAfter' time. If not, is there a workaround to achieve this?

At a certain point, I queue up the following DispatchWorkItem:

dispatchWorkItem = DispatchWorkItem(qos: .userInteractive, block: {
            self.view.backgroundColor = UIColor.workoutBackgroundColor
            self.runTimer()
            self.timerButton.animateableTrackLayer.removeAnimation(forKey: "strokeEndAnimation")
            self.isRestState = false
        })

I queue this up using:

    DispatchQueue.main.asyncAfter(deadline: delayTime, execute: self.dispatchWorkItem))

(delayTime being a parameter to the function)

Now, the problem I am running into is how can I suspend this work item if the user performs a 'pause' action in my app.

I have tried using the DispatchQueue.main.suspend() method but the work item continues to execute after the specified delay time. From what I have read, this method should suspend the queue and this queued work item since it is not being executed. (Please correct me if I am wrong there!)

What I need to achieve is the work item is 'paused' until the user performs the 'resume' action in the app which will resume the work item from where the delay time left off.

This works on background queues that I have created when I do not need to make UI updates; however, on the main queue is appears to falter.

One workaround I have considered is when the user performs the pause action, storing the time left until the work item was going to be executed and re-adding the work item to the queue with that time on the resume action. This seems like a poor quality approach and I feel there is a more appropriate method to this.

On that, is it possible to create a background queue that on execution, executes a work item on the main queue?

Thanks in advance!

Alex Marchant
  • 570
  • 6
  • 21
  • @matt I am using the ‘asyncAfter’ method which allows code and user interaction with the app to continue until the work item is executed, when it is executed the user will not be able to perform the pause action. – Alex Marchant Dec 02 '18 at 21:22
  • Possible duplicate of https://stackoverflow.com/questions/29492707/how-to-stop-cancel-suspend-resume-tasks-on-gcd-queue where it is made clear that you cannot suspend a queue that isn't yours (and the main queue is not yours). – matt Dec 02 '18 at 22:11

1 Answers1

3

On that, is it possible to create a background queue that on execution, executes a work item on the main queue?

You are suggesting something like this:

var q = DispatchQueue(label: "myqueue")
func configAndStart(seconds:TimeInterval, handler:@escaping ()->Void) {
    self.q.asyncAfter(deadline: .now() + seconds, execute: {
        DispatchQueue.main.async(execute: handler())
    })
}
func pause() {
    self.q.suspend()
}
func resume() {
    self.q.resume()
}

But my actual tests seem to show that that won't work as you desire; the countdown doesn't resume from where it was suspended.

One workaround I have considered is when the user performs the pause action, storing the time left until the work item was going to be executed and re-adding the work item to the queue with that time on the resume action. This seems like a poor quality approach and I feel there is a more appropriate method to this.

It isn't poor quality. There is no built-in mechanism for pausing a dispatch timer countdown, or for introspecting the timer, so if you want to do the whole thing on the main queue your only recourse is just what you said: maintain your own timer and the necessary state variables. Here is a rather silly mockup I hobbled together:

class PausableTimer {
    var t : DispatchSourceTimer!
    var d : Date!
    var orig : TimeInterval = 0
    var diff : TimeInterval = 0
    var f : (()->Void)!
    func configAndStart(seconds:TimeInterval, handler:@escaping ()->Void) {
        orig = seconds
        f = handler
        t = DispatchSource.makeTimerSource()
        t.schedule(deadline: DispatchTime.now()+orig, repeating: .never)
        t.setEventHandler(handler: f)
        d = Date()
        t.resume()
    }
    func pause() {
        t.cancel()
        diff = Date().timeIntervalSince(d)
    }
    func resume() {
        orig = orig-diff
        t = DispatchSource.makeTimerSource()
        t.schedule(deadline: DispatchTime.now()+orig, repeating: .never)
        t.setEventHandler(handler: f)
        t.resume()
    }
}

That worked in my crude testing, and seems to be interruptible (pausable) as desired, but don't quote me; I didn't spend much time on it. The details are left as an exercise for the reader!

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Actually my tests suggest that the first approach doesn't work at all. I'm taking it back out of my answer. – matt Dec 03 '18 at 20:18
  • I have experienced the same issue when testing it too, I will proceed forward with the timing method. – Alex Marchant Dec 03 '18 at 20:38