How to cancel the delayed thread? - swift

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

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:
Schedule a Timer and then invalidate it if you want to stop it.
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.
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.

Related

Swift Async - what thread does Task.detached run on?

I've written a global function that allows me to synchronously wait for the completion of an async task (for experimentation purposes only!) with the help of a DispatchSemaphore.
///
/// synchronously waits for the completion of an asynchronous closure
/// - parameter handler: the task to run, asynchronously
/// - note: don't use this in production code, it violates
/// the Swift runtime contract of "forward" progress, it's never
/// safe to block a thread and wait for another thread to unblock it
/// (on a single core system, this may hang forever)
///
func sync(handler: #escaping () async -> Void) {
let sema = DispatchSemaphore(value: 0)
// default `Task.Priority` to `asyncDetached` is `nil`
Task.detached {
await handler()
sema.signal()
}
// blocks the current thread, waiting for the async Task to finish
sema.wait()
}
I noticed that if I just use an Task block to launch the asynchronous tasks a deadlock arises, presumably because sema.wait() is blocking the current thread, preventing the Task block from ever running (as normal Task blocks seem to inherit the current thread), so it will just wait forever.
Using Task.detached does not seem to block the thread, making the above code work.
This appears to be related to the Task.Priority? to which the detached call is assigned.
The default parameter value is to which is nil when calling Task.detached.
This can be customized to a different priority level, indicating the dispatch QoS.
My questions therefore is: what thread does Task.Priority? = nil relate to? It does not appear to ever be the same thread as where it was launched from. Does Task.Priority? = nil indicate to the Swift runtime to always run on a different thread?
EDIT
Follow on question - how do Task and Task.detached differ in regard to threading?
The priority does not influence the priority on your app's side. It is a hint for the host to response to several requests in a priority. The host can completely ignore that.
refer to this answer for more information.
Therefore task priority has nothing to do with the thread with that aside.
you'r task is getting dispatched every time and Task.Priority refers to no thread and has nothing to do with threads.
Rather than using semaphores and asyncDetached, I suggest using GCD queues and DispatchGroups. They are lightweight and very easy to use. You just
Create a DispatchGroup (let group = DispatchGroup(), call group.notify(queue:work:) to submit a block to be run when the group of tasks is complete.
Call group.enter() each time you begin a task you want to wait for,
Call group.leave() when you complete a task
The system will then invoke the block you submitted in your notify(queue:work:) call once the tasks are complete. That's all there is to it. You can submit 0, one, or many tasks. If you haven't submitted any, or they have all completed, your notify closure fires immediately. If some tasks are still running, your notify closure isn't called until your tasks call group.leave()

AKMetronome countdown, how to publish to main thread from background thread to start recorder?

I've been looking into how to implement a Metronome countdown with the AudioKit lib but ran into an issue related to threads and my implementation.
My implementation uses the AudioKit AKMetronome, a method to handle the metronome.start and the metronome.callback handler to start recording after a given number of bars are completed.
init () {
metronome.callback = handler
}
func record () {
self.metronome.start()
}
The handler computes the metronome position in time and if the desired number of bars completes (countdown), the recorder starts.
Unfortunately, running the .record of AKNodeRecorder in the .callback handler of AKMetronome causes the warning message:
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
For that reason, the call to start recording in the metronome.callback handler is passed to the main thread through the GCD API:
DispatchQueue.main.sync {
do {
try self.recorder.record()
} catch {
print("[ERROR] Oops! Failed to record...")
}
}
I've used the .sync blocking method to resolve the computation immediately as possible since
timing is critical in audio applications (the call should be executed in a real-time thread, ideally); Is my understanding that the GCP API main thread provides the highest priority, but I'm not certain if that's the best option for a time-sensitive application?
Would like to get some feedback or direction if possible from other users, thank you!
OK, so the actual question has nothing to do with metronomes or countdowns? What you really want to know is: will using sync from a background thread get me onto the main thread faster?
If so: Basically no. This is not what sync is for or what it means. The async/sync difference has absolutely no effect on speed. If you get onto the main thread using async, you will get on as soon it is free, so you gain nothing whatever by saying sync.
Moreover, if you already arrived into the callback in an async background thread, any damage that can be done is already done; your timing is now inexact and there is absolutely nothing you can do about it unless you happen to have a time machine in your pocket.
Following up on #matt advice regarding calls inside callbacks, the approach I took change based in a .scheduledTimer of Timer.
func record () {
metronome.restart()
Timer.scheduledTimer(withTimeInterval: self.barLength, repeats: false) { _ in
self.startRecording()
print("Timer fired!")
}
}
The record is a handler that initiates the AKMetronome, opted to use .restart so that every time's requested starts from the very start.
A property .barLength holds the value for the computed length of the desired countdown. The .startRecording method that is called, is a handler that takes care of AKRecorder .record call.
Future reader, have in mind that Timer is not meant to be accurate according to (Accuracy of NSTimer) but
unfortunately, this is the best approach I've tested and I found so far.

Dispatch Queues - Nesting, Optimisation & Control

Can I have your advice on the approved approach?
I have four processes that need to run sequentially:
calculateProcess1()
calculateProcess2()
calculateProcess3()
calculateProcess4()
These calculate data and update progress bars (circular) and other screen literals using dispatch queues to make the UI update.
Run them as is and lo and behold they all fire off simultaneously (what a surprise!).
How do I make the top level calls run in sequence (as above) and do I need to make changes to my DispatchQueues in the processes for updating the literals, e.g.:
DispatchQueue.main.sync {
let progressPercentage = (day*100/365)
self.progressLabel.text = String(Int(progressPercentage))+"%"
}
The initiating processes (calculateProcess1-4()) need to run from main. Should this be in ViewDidLoad, a button action or what is the most secure method?
A one approach would be to use Operation and OperationQueue. Setting maxConcurrentOperationCount: Int to 1 on the OperationQueue, will force operations to be performed in a sequence, one after another.
(Previously called NSOperation and NSOperationQueue)
This blogpost can be helpful: https://nshipster.com/nsoperation/
And of course awesome WWDC15 session: Advanced NSOperations
1- Don't sync in main thread as it'll cause a runtime crash , it should be async
DispatchQueue.main.sync {
2-
Should this be in ViewDidLoad, a button action or what is the most secure method?
it's up to you there is no thing related to security here , implement it as your UX
3- to run them in dequence create a custom Queue then dispatch them in it as it' ll run serially if the code inside all these methods run in the same thread of the queue meaning you don't internally dispatch inside another queue , you can also use DispatchGroup to be notified when all are done
If you want to keep it simple you can provide callback blocks to your calculateProcess methods and simply nest them.
calculateProcess1() {
calculateProcess2() {
calculateProcess3() {
calculateProcess4() {}
}
}
}
Should this be in ViewDidLoad, a button action or what is the most
secure method?
viewDidLoad is probably what you want. Keep in mind if you instantiate a new viewController of the same type and show it, it will happen again, once, for that new controller as well. Also, if you are doing anything with view frames, those are not guaranteed to be laid out in viewDidLoad, but if you are simply updating text then it should be fine.

Swift: synchronously perform code in background; queue.sync does not work as I would expect

I would like to perform some code synchronously in the background, I really thought this is the way to go:
let queue = DispatchQueue.global(qos: .default)
queue.async {
print("\(Thread.isMainThread)")
}
but this prints true unless I use queue.async. async isn't possible as then the code will be executed in parallel. How can I achieve running multiple blocks synchronously in the background?
What I would like to achieve: synchronize events in my app with the devices calendar, which happens in the background. The method which does this can be called from different places multiple times so I would like to keep this in order and in the background.
Async execution isn't your problem, since you only care about the order of execution of your code blocks relative to each other but not relative to the main thread. You shouldn't block the main thread, which is in fact DispatchQueue.main and not DispatchQueue.global.
What you should do is execute your code on a serial queue asynchronously, so you don't block the main thread, but you still ensure that your code blocks execute sequentially.
You can achieve this using the following piece of code:
let serialQueue = DispatchQueue(label: "serialQueue")
serialQueue.async{ //call this whenever you need to add a new work item to your queue
//call function here
}
DispatchQueue is not equal to a Thread. Think of it as of a kind of abstraction over the thread pool.
That being said, main queue is indeed "fixed" on the main thread. And that is why, when you synchronously dispatch a work item from the main queue, you are still on the main thread.
To actually execute sync code in the background, you have to already be in the background:
DispatchQueue.global().async {
DispatchQueue.global().sync {
print("\(Thread.isMainThread)")
}
}
This will print false.
Also, as user #rmaddy correctly pointed out in comments, doing any expensive tasks synchronously from the main queue might result in your program becoming unresponsive, since the main thread is responsible for the UI updates.

RunLoopObserver and RunLoopActivity

In the code that I show below, I have created a thread that creates random numbers between 0 to 15, and it stops when it comes out 3, changing the end parameter. After I added a run loop observer (that "observe" the end parameter) to the run loop of the main thread. As you can see, both the run loop observer and my thread, sleep for 1 second before printing, so I expect that in the console, observer's print and the print of my thread is alternate. Which is not the case. I believe, if I understand it, it would depend on CFrunloopActivity parameter and its possible combinations.
Does anyone can explain the operation of this parameter?
If yes, there is a combination to have alternating prints? If you can not have alternating prints, how does the observer work inside the run loop of the main thread ?
Thank you
This is the code :
class ViewController: UIViewController {
var end = false
override func viewDidLoad() {
super.viewDidLoad()
//my thread
performSelector(inBackground: #selector(rununtil3(thread:)), with: Thread.current)
//the observer
let runLoopObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.entry.rawValue | CFRunLoopActivity.exit.rawValue , true , 0 , {
(observer: CFRunLoopObserver?, activity: CFRunLoopActivity) -> Void in
Thread.sleep(until: Date(timeIntervalSinceNow: 1))
print("+++ is main?: \(Thread.isMainThread)")
if self.end == true {
//print the end of my thread and remove the observer from main run loop
print("end of own thread")
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, CFRunLoopMode.commonModes)
return
}
//CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, CFRunLoopMode.commonModes)
})
//add observer to main run loop
CFRunLoopAddObserver(CFRunLoopGetCurrent(), runLoopObserver, CFRunLoopMode.commonModes)
print("Out of observer")
}
func rununtil3(thread : Thread) {
print("main?: \(thread.isMainThread) is run : \(thread.isExecuting)")
while true {
let ran = Int (arc4random() % 15 )
Thread.sleep(until: Date(timeIntervalSinceNow: 1))
print("\(ran) . main is run : \(thread.isExecuting)")
if ran == 3 {
end = true
Thread.exit()
}
}
}
}
You are creating a runloop observer and asking to be notified when the runloop either starts operation (its enter event) or exits. Runloop activities are documented here.
enter events are delivered when a runloop is started by CFRunLoopRun() or similar. If you manually create a runloop, add an enter observer to it, then call CFRunLoopRun() on your new runloop, you will receive an enter event at that time. If you later call CFRunLoopStop() on your runloop, you will receive an exit event.
When you add an enter observer to an already running runloop, you will receive an enter event. This is to keep your observer state consistent with the actual state of the runloop.
In your code, you create a runloop observer then attach it to the main thread's runloop (aka the "Main Runloop").
The OS creates the Main Runloop automatically for you at program start and automatically calls CFRunLoopRun() on it. CFRunLoopStop() is never called, so the main runloop effectively runs forever.
Since the main runloop is already running, you receive an enter event. Since the main runloop does not stop, you never see an exit event.
It's important to note that a runloop observer is bound to the specific runloop you add it to, not to the lifecycle of some arbitrary background thread or property (i.e. your end property is not the thing being observed).
To your second question about how to get threads to alternate, that's a question with a very broad answer and it depends highly on what you want to do. I won't attempt to answer all of that here, only give you some ideas.
You might be best served by not creating a background thread at all, and instead adding a timer to the main runloop that fires every second. Then you'll get periodic behavior.
If you really want to use a background thread, then you should read a good operating systems book on thread communication and synchronization. A common thing to do on iOS/OS X is to use a background thread, then use something like DispatchQueue.main.async { } to signal back to the main thread that your processing is complete. There are many examples of how to do this if you search a bit.
You might also read about thread synchronization using semaphores or condition variables. One thing you definitely do NOT want to do is call Thread.sleep() on the main thread as you are doing in your observer callback. If you wait on the main thread too long the operating system will kill your app. It's better to keep background threads totally independent of the main thread and then call back onto the main thread using a DispatchQueue call as I mentioned above in #2.