Search code examples
swiftmultithreadingios-multithreading

How to cancel the delayed thread?


I am executing set of tasks, and in order to span them over time I've used Thread.sleep(forTimeInterval: ... ) It adds pauses between executions.

Then, on the main screen I have controllers, that can intervene into thread execution, so when controller applied I want to cancel the sleep. And start from the place of user's choice. So, I've tried Thread.exit() - and it terminates the program with error.

How to exit (or, refresh) the current thread and start from the place I want? What are the ways to control the suspension of thread by button (i.e. controller)?

What actually happens is that if I cancel, it continues to run just from the same place...

let queue = DispatchQueue(label: "Actions Queue")
let delay: Double = 5
var actions = ["action 1", "action 2", "action 3", "action 4", "action 5"]


// every time task finishes, it calls for the following task (with delay)
queue.sync {
// sets an interval, that continues an action
    Thread.sleep(forTimeInterval: TimeInterval(delay))
    // continue an action
}

// heres how I've tried in main view, but it doesn't work
queue.sync {
    Thread.current.cancel()
}

EDITS:

For example, what does the cancel() do here? and why exit() can't be applied to Thread.current, but only to Thread.

"You can use these semantics to cancel the execution of a thread or determine if the thread is still executing or has finished its task". Okey, if I cancel(). What, then will start it again?

queue.sync {
    Thread.sleep(forTimeInterval: TimeInterval(10.0))
    // do something
}

Button("...") {
    queue.sync {
        Thread.current.cancel()
    }
}

https://developer.apple.com/documentation/foundation/thread


Solution

  • As a general rule, I would advise against sleeping on a thread. First, we do not have preemptive cancelation. Second, it’s an inefficient use of resources, tying up that thread unnecessarily.

    If you want to do something after a certain period of time, you have a few options:

    1. Schedule a Timer and then invalidate it if you want to stop it.

    2. If using Swift concurrency (e.g., Task, async-await, etc.), you can Task.sleep, which, unlike Thread.sleep, detects and handles cancelation. Thread.sleep should be avoided, but with Swift concurrency, you can store the Task in a property, cancel it when appropriate, and Task.sleep within the Task will handle cancellation elegantly.

    3. You can create a DispatchWorkItem, dispatch it with asyncAfter, and then cancel if you don’t want that to happen.

    If you have some cancelable tasks that you want to perform every x seconds, a single repeating Timer is probably easiest, as you can invalidate it and it stops the whole repeating series in one step. (It also avoids timer coalescing problems.)


    You said:

    what I am trying to achieve is - having a presentation that runs with predetermined time between slides, but if user wants to go back (or forward); it should cut straight away to previous slide and continue in the same manner of predetermined pauses.

    I would suggest a repeating Timer to advance to the next slide. If the user manually changes to another slide, invalidate the old timer and create a new repeating Timer.