RxSwift: ensuring current thread in subscribe closure - swift

The question is very simple. Is it guaranteed that, without calling observeOn() - i.e. using CurrentThreadScheduler - the closure of subscribe() is executed in the same thread (not queue) of the call?
In the example starting thread == observer thread
// <starting thread>
let observable = ... // an observable
observable.subscribe(onNext: { _ in
// <observer thread>
})

It's said here that
When we are doing some operations with Rx, by definition it is all done on the same thread. Unless you don’t change the thread manually, entry point of the chain will begin on the current thread and it will also dispose on the same thread.
Also, the default scheduler is the CurrentThreadScheduler, which schedules on the current thread

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

Why does DispatchSemaphore.wait() block this completion handler?

So I've been playing about with NetworkExtension to to make a toy VPN implementation and I ran into an issue with the completion handlers/asynchronously running code. I'll run you through my train of thought/expirments and would appreciate any pointers at areas where I am mistaken, and how to resolve this issue!
Here's the smallest reproducible bit of code (obviously you will need to import NetworkExtension):
let semaphore = DispatchSemaphore(value: 0)
NETunnelProviderManager.loadAllFromPreferences { managers, error in
print("2 during")
semaphore.signal()
}
print("1 before")
semaphore.wait()
print("3 after")
With my understanding of semaphores and asynchronous code I'd expect the printouts to occur in the order:
1 before
2 during
3 after
However the program hangs at "1 before". If I remove the semaphore.wait() line, the printout occurs as expected in the order: 1, 3, 2 (as the closure runs later).
So after a bit of digging around with the debugger, it looks like the semaphore trap loop is blocking up execution. This sparked me to read around a bit into queues, and I discovered that changing it to the following works:
// ... as before
DispatchQueue.global().async {
semaphore.wait()
print("3 after")
}
This makes some sense as the blocking .wait() call is now being called asynchronously in a separate thread. However, this solution is not desired for me as in my actual implementation I am actually capturing the results from the closure and returning them later, in something that looks like this:
let semaphore = DispatchSemaphore(value: 0)
var results: [NETunnelProviderManager]? = nil
NETunnelProviderManager.loadAllFromPreferences { managers, error in
print("2 during")
results = managers
semaphore.signal()
}
print("1 before")
// DispatchQueue.global().async {
semaphore.wait()
print("3 after")
// }
return results
Obviously I cannot return data from from the async closure, and moving the return out of it would make it defunct. Acdditionally, adding another semaphore to make things synchronous exhibits the same issue as before just moving the problem along in a chain.
As a result, I decided to try putting the .loadAllFromPreferences() call and completion handler in an async closure and leave everything else as in the original code snippet:
// ...
DispatchQueue.global().async {
NETunnelProviderManager.loadAllFromPreferences { loadedManagers, error in
print("2 during")
semaphore.signal()
}
}
// ...
However this does not work and the .wait() call is never passed - as before. I assume that somehow the sempahore is still blocking the thread and not allowing anything to execute, meaning whatever in the system is managing the queue is not running the async block? However I'm clutching at straws here, and fear my original conclusion may not have been right.
This is where I'm starting to get out of my depth, so I'd like to know what is actually going on, and what resolution would you recommend to get the results from .loadAllFromPreferences() in a synchronous manner?
Thanks!
From the documentation for NETunnelProviderManager loadAllFromPreferences:
This block will be executed on the caller’s main thread after the load operation is complete
So we know that the completion handler is on the main thread.
We also know that the call to DispatchSemaphore wait will block whatever thread it is running on. Given this evidence, you must be calling all of this code from the main thread. Since your call to wait is blocking the main thread, the completion handler can never be called because the main thread is blocked.
This is made clear by your attempt to call wait on some global background queue. That allows the completion block to be called because your use of wait is no longer blocking the main thread.
And your attempt to call loadAllFromPreferences from a global background queue doesn't change anything because its completion block is still called on the main thread and your call to wait is still on the main thread.
It's a bad idea to block the main thread at all. The proper solution is to refactor whatever method this code is in to use its own completion handler instead of trying to use a normal return value.

Block in sync method on background queue is executed on main thread

Just started to learning about GCD and I am running into trouble because my code is still ran on the main thread while I created a background queue. This is my code:
import UIKit
class ViewController: UIViewController {
let queue = DispatchQueue(label: "internalqueue", qos: .background)
override func viewDidLoad() {
super.viewDidLoad()
dispatchFun {
assert(Thread.isMainThread)
let x = UIView()
}
}
func dispatchFun(handler: #escaping (() -> ())) {
queue.sync {
handler()
}
}
}
Surprising enough (for me), is that this code doesn't throw any error! I would expect the assertion would fail. I would expect the code is not ran on the main thread. In the debugger I see that when constructing the x instance, that I am in my queue on thread 1 (by seeing the label). Strange, because normally I see the main thread label on thread 1. Is my queue scheduled on the main thread (thread 1)?
When I change sync for async, the assertion fails. This is what I would expect to happen with sync aswell. Below is an attached image of the threads when the assertion failed. I would expect to see the exact same debug information when I use sync instead of async.
When reading the sync description in the Swift source, I read the following:
/// As an optimization, `sync(execute:)` invokes the work item on the thread which
/// submitted it, except when the queue is the main queue or
/// a queue targetting it.
Again: except when the queue is the main queue
Why does the sync method on a background dispatch queue cases the code to run on the main thread, but async doesn't? I can clearly read that the sync method on a queue shouldn't be ran on the main thread, but why does my code ignore that scenario?
I believe you’re misreading that comment in the header. It’s not a question of whether you’re dispatching from the main queue, but rather if you’re dispatching to the main queue.
So, here is the well known sync optimization where the dispatched block will run on the current thread:
let backgroundQueue = DispatchQueue(label: "internalqueue", attributes: .concurrent)
// We'll dispatch from main thread _to_ background queue
func dispatchingToBackgroundQueue() {
backgroundQueue.sync {
print(#function, "this sync will run on the current thread, namely the main thread; isMainThread =", Thread.isMainThread)
}
backgroundQueue.async {
print(#function, "but this async will run on the background queue's thread; isMainThread =", Thread.isMainThread)
}
}
When you use sync, you’re telling GCD “hey, have this thread wait until the other thread runs this block of code”. So, GCD is smart enough to figure out “well, if this thread is going to not do anything while I’m waiting for the block of code to run, I might as well run it here if I can, and save the costly context switch to another thread.”
But in the following scenario, we’re doing something on some background queue and want to dispatch it back to the main queue. In this case, GCD will not do the aforementioned optimization, but rather will always run the task dispatched to the main queue on the main queue:
// but this time, we'll dispatch from background queue _to_ the main queue
func dispatchingToTheMainQueue() {
backgroundQueue.async {
DispatchQueue.main.sync {
print(#function, "even though it’s sync, this will still run on the main thread; isMainThread =", Thread.isMainThread)
}
DispatchQueue.main.async {
print(#function, "needless to say, this async will run on the main thread; isMainThread =", Thread.isMainThread)
}
}
}
It does this because there are certain things that must run on the main queue (such as UI updates), and if you’re dispatching it to the main queue, it will always honor that request, and not try to do any optimization to avoid context switches.
Let’s consider a more practical example of the latter scenario.
func performRequest(_ url: URL) {
URLSession.shared.dataTask(with: url) { data, _, _ in
DispatchQueue.main.sync {
// we're guaranteed that this actually will run on the main thread
// even though we used `sync`
}
}
}
Now, generally we’d use async when dispatching back to the main queue, but the comment in the sync header documentation is just letting us know that this task dispatched back to the main queue using sync will actually run on the main queue, not on URLSession’s background queue as you might otherwise fear.
Let's consider:
/// As an optimization, `sync(execute:)` invokes the work item on the thread which
/// submitted it, except when the queue is the main queue or
/// a queue targetting it.
You're invoking sync() on your own queue. Is that queue the main queue or targeting the main queue? No, it's not. So, the exception isn't relevant and only this part is:
sync(execute:) invokes the work item on the thread which submitted it
So, the fact that your queue is a background queue doesn't matter. The block is executed by the thread where sync() was called, which is the main thread (which called viewDidLoad(), which called dispatchFun()).

Using completion handler together with DispatchQueue

I have learned that concurrent DispatchQueue allows the code inside it to return immediately, hence not blocking the calling thread. This is usually used with background tasks loading large data.
I have also learned that completion handler (for example, in URLSession ) allows the code inside handler to be executed after some task finishes.
My question is: does it mean that concurrent dispatch queue and completion handler have overlapping purpose? If I already used completion handler, there is no need to wrap it with a concurrent dispatch queue?
For example, below is a time-consuming data loading task using URLSession, is it a good idea to wrap it with a concurrent dispatch queue?
URLSession(configuration: URLSessionConfiguration.default).dataTask(with: propertiesRequest) { data, response, error in
// print("within dataTask: data: \(data), response: \(response), error: \(error)")
if let error = error {
print(error)
} else if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode == 200 {
print("success: property task request")
do {
handler(responseDict, nil) // This is a function supplied by caller
} catch let error as NSError {
handler(nil, error)
}
}
}
}
You don't have to use Grand Central Dispatch (GCD) dispatch queues in conjunction with time-consuming URLSession request.
You'd might use GCD inside the dataTask completion handler closure if:
If you are doing something inside the closure is, itself, very time consuming (e.g. processing very complicated request) and you don't want to tie up the serial operation queue that URLSession uses for processing its completion handlers (and delegate method). This does not appear to be the issue here (e.g. parsing JSON responses is generally quick enough we don't have to worry about this), but just FYI. Or,
If, when you're done parsing the response of dataTask, if you want to update some model object or update the UI. You want to do those on the main queue.
For example, if your request was returning a bunch of objects to show in some tableview, you'd dispatch the update of the model and the UI to the main queue:
DispatchQueue.main.async {
self.objects = ...
self.tableView.reloadData()
}
But you don't have to worry about the time-consuming URLSession request, itself. That already happens asynchronously, so you don't have to dispatch that to a background queue.
The DispatchQueue and the completion handler do not overlap but rather can be used as a seamless solution for handling queues. The data loading task in URLSession is already asynchronous and thus have no need to be wrapped in a DispatchQueue.
DispatchQueue - assigning tasks to a specific thread for better performance (global queue) / user experience (main queue).
Completion Handlers - guarantees certain code will run when the task has been completed. However, the execution is on the current thread unless explicitly stated otherwise.
For example, calling on method A which employs DispatchQueue.global.async will queue the task on the global queue, freeing the main queue for more important (UI) tasks. After some time, the task would have been completed and usually, we would want to do something with the data. If it is UI related, we definitely want to call on DispatchQueue.main.async to update the screen with info or if it is trivial, no calls need to be made and vanilla code would suffice.

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.