The callback inside Task is automatically called on the main thread - swift

Once upon the time, before Async/Await came, we use to make a simple request to the server with URLSession dataTask. The callback being not automatically called on the main thread and we had to dispatch manually to the main thread in order to perform some UI work. Example:
DispatchQueue.main.async {
// UI work
}
Omitting this will lead to the app to crash since we try to update the UI on different queue than the main one.
Now with Async/Await things got easier. We still have to dispatch to the main queue using MainActor.
await MainActor.run {
// UI work
}
The weird thing is that even when I don't use the MainActor the code inside my Task seems to run on the main thread and updating the UI seems to be safe.
Task {
let api = API(apiConfig: apiConfig)
do {
let posts = try await api.getPosts() // Checked this and the code of getPosts is running on another thread.
self.posts = posts
self.tableView.reloadData()
print(Thread.current.description)
} catch {
// Handle error
}
}
I was expecting my code to lead to crash since I am trying to update the table view theorically not from the main thread but the log says I am on the main thread. The print logs the following:
<_NSMainThread: 0x600003bb02c0>{number = 1, name = main}
Does this mean there is no need to check which queue we are in before performing UI stuff?

Regarding Task {…}, that will “create an unstructured task that runs on the current actor” (see Swift Concurrency: Unstructured Concurrency). That is a great way to launch an asynchronous task from a synchronous context. And, if called from the main actor, this Task will also be on the main actor.
In your case, I would move the model update and UI refresh to a function that is marked as running on the main actor:
#MainActor
func update(with posts: [Post]) async {
self.posts = posts
tableView.reloadData()
}
Then you can do:
Task {
let api = API(apiConfig: apiConfig)
do {
let posts = try await api.getPosts() // Checked this and the code of getPosts is running on another thread.
self.update(with: posts)
} catch {
// Handle error
}
}
And the beauty of it is that if you’re not already on the main actor, the compiler will tell you that you have to await the update method. The compiler will tell you whether you need to await or not.
If you haven’t seen it, I might suggest watching WWDC 2021 video Swift concurrency: Update a sample app. It offers lots of practical tips about converting code to Swift concurrency, but specifically at 24:16 they walk through the evolution from DispatchQueue.main.async {…} to Swift concurrency (e.g., initially suggesting the intuitive MainActor.run {…} step, but over the next few minutes, show why even that is unnecessary, but also discuss the rare scenario where you might want to use this function).
As an aside, in Swift concurrency, looking at Thread.current is not reliable. Because of this, this practice is likely going to be prohibited in a future compiler release.
If you watch WWDC 2021 Swift concurrency: Behind the scenes, you will get a glimpse of the sorts of mechanisms underpinning Swift concurrency and you will better understand why looking at Thread.current might lead to all sorts of incorrect conclusions.

Related

Swift Concurrency : Why Task is not executed on other background thread

I am trying to learn the swift concurrency but it brings in a lot of confusion. I understood that a Task {} is an asynchronous unit and will allow us to bridge the async function call from a synchronous context. And it is similar to DispatchQueue.Global() which in turn will execute the block on some arbitrary thread.
override func viewDidLoad() {
super.viewDidLoad()
Task {
do {
let data = try await asychronousApiCall()
print(data)
} catch {
print("Request failed with error: \(error)")
}
}
for i in 1...30000 {
print("Thread \(Thread.current)")
}
}
my asychronousApiCall function is below
func asychronousApiCall() async throws -> Data {
print("starting with asychronousApiCall")
print("Thread \(Thread.current)")
let url = URL(string: "https://www.stackoverflow.com")!
// Use the async variant of URLSession to fetch data
// Code might suspend here
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
When I try this implementation. I always see that "starting with asychronousApiCall" is printed after the for loop is done and the thread is MainThread.
like this
Thread <_NSMainThread: 0x600000f10500>{number = 1, name = main}
You said:
I understood that a Task {} is an asynchronous unit and will allow us to bridge the async function call from a synchronous context.
Yes.
You continue:
And it is similar to DispatchQueue.global() which in turn will execute the block on some arbitrary thread.
No, if you call it from the main actor, it is more akin to DispatchQueue.main.async { … }. As the documentation says, it “[r]uns the given nonthrowing operation asynchronously as part of a new top-level task on behalf of the current actor” [emphasis added]. I.e., if you are currently on the main actor, the task will be run on behalf of the main actor, too.
While it is a probably mistake to dwell on direct GCD-to-concurrency mappings, Task.detached { … } is more comparable to DispatchQueue.global().async { … }.
You commented:
Please scroll to figure 8 in last of the article. It has a normal task and Thread is print is some other thread.
figure 8
In that screen snapshot, they are showing that prior to the suspension point (i.e., before the await) it was on the main thread (which makes sense, because it is running it on behalf of the same actor). But they are also highlighting that after the suspension point, it was on another thread (which might seem counterintuitive, but it is what can happen after a suspension point). This is very common behavior in Swift concurrency, though it can vary.
FWIW, in your example above, you only examine the thread before the suspension point and not after. The take-home message of figure 8 is that the thread used after the suspension point may not be the same one used before the suspension point.
If you are interested in learning more about some of these implementation details, I might suggest watching WWDC 2021 video Swift concurrency: Behind the scenes.
While it is interesting to look at Thread.current, it should be noted that Apple is trying to wean us off of this practice. E.g., in Swift 5.7, if we look at Thread.current from an asynchronous context, we get a warning:
Class property 'current' is unavailable from asynchronous contexts; Thread.current cannot be used from async contexts.; this is an error in Swift 6
The whole idea of Swift concurrency is that we stop thinking in terms of threads and we instead let Swift concurrency choose the appropriate thread on our behalf (which cleverly avoids costly context switches where it can; sometimes resulting code that runs on threads other than what we might otherwise expect).

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.

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.

Is there any point in querying realm on a background thread and resolving a ThreadSafeReference on the UI thread?

It appears that ThreadSafeReference was added recently to help move across thread boundaries. Prior, according to the sources I read (which were probably not exhaustive) the recommendation to was to just query realm on the thread you intend to use the results on; effectively query it on the UI thread.
Is there a benefit to querying Realm on a background thread or does resolving the ThreadSafeReference basically run the query again?
Using RxSwift here's an example of this:
import RxSwift
import RealmSwift
public static func getAllMyModels() -> Observable<Results<MyModel>>{
return Observable<ThreadSafeReference<Results<MyModel>>>.create{
observer in
// using this queue in this example only
DispatchQueue.global(qos: .default).async {
let realm = try! Realm()
let models = realm.objects(MyModel.self)
let safe = ThreadSafeReference(to: models)
observer.onNext(safe)
observer.onCompleted()
}
return Disposables.create()
}
.observeOn(MainScheduler.instance) // push us back to the UI thread to resolve the reference
.map{
safeValue in
let realm = try! Realm()
let value = realm.resolve(safeValue)!
return value
}
.shareReplayLatestWhileConnected()
}
Did I gain anything by querying on some background thread and resolving on the UI thread?
Seems unnecessary. According to the docs, queries are already being done on a background thread, as long as you have attached a notification block:
Once the query has been executed, or a notification block has been added, the Results is kept up to date with changes made in the Realm, with the query execution performed on a background thread when possible.
- https://realm.io/docs/swift/latest/#queries
ast's guidance is correct, but I dug a little more and wanted to post some extra to confirm his answer further.
kishikawa-katsumi, currently a software engineer at Realm, provided this response to the question in Realm's public slack (https://realm-public.slack.com/archives/general/p1488960777001796):
For querying, it is fast enough in UI thread in most cases. If you're facing about a few slow complex queries, you can use background query.
To execute queries in the background, use addNotificationBlock ().
notificationToken = realm
.objects(...)
.filter(...)
.addNotificationBlock { (changes) in
// The query is executed in background.
// When the query is completed, then call this block
...
}
Using addNotificationBlock(), the query is excuted in background, when the query is completed, then call the callback closure will be called.
So ThreadSafeReference is rarely used in queries. ThreadSafeReference is used when you want to pass an object to another thread (for example, to specify it as a condition of a query or to use it as a parameter of an API request).
Additional information about subscribing to this block from a GCD thread (background thread) can be found here, as it requires a runloop.
https://stackoverflow.com/a/41841847/1060314

A solution to track a batch of HTTP requests in swift 3.0

I am using swift 3.0 running under iOS 10.0 and I want to craft some code that fires when a batch condition is met.
for i in 0 ..< rex {
async code, disappears and does it stuff
}
Imagine the async code is a collection of URL requests, that basically background as soon as I loop thru them. Now how can I fire off more code when "rex" requests have completed?
I thought of setting up a timer to watch and check every second, but its surely not a good solution.
I thought kicking off another thread to simply watch the data being collected, and fire when its quota is full, but well that's worse then the timer really.
I am thinking to include a test at the end of each URL request to see if it was the last that completed and than uses the NotificationCenter, but is this the optimal solution?
While OperationQueue (aka NSOperationQueue) is a good choice in many cases, it's not suitable for your use case. The problem is that URL requests are called asynchronously. Your NSOperation will finish before you get a response from the webservice.
Use DispatchGroup instead
let group = DispatchGroup()
// We need to dispatch to a background queue because we have
// to wait for the response from the webservice
DispatchQueue.global(qos: .utility).async {
for i in 0 ..< rex {
group.enter() // signal that you are starting a new task
URLSession.shared.dataTask(with: urls[i]) { data, response, error in
// handle your response
// ....
group.leave() // signal that you are done with the task
}.resume()
}
group.wait() // don't ever call wait() on the main queue
// Now all requests are complete
}
So I'm pretty sure what you want can be found here. Basically you want to use GCD and have a completion closure. It's one line of code, which always makes me giggle. A longer post on the topic is here.
What you're looking for is NSOperationQueue (or OperationQueue in Swift 3). Here's a Swift tutorial (might be a bit out of date). Here's Apple's documentation on it -- in Swift 3 they drop all the NS prefixes, so it's OperationQueue / Operation.
Basically you should add each of your URL tasks as an Operation to an OperationQueue, and have a "done" Operation with each of your URL tasks as a dependency, and add it to the queue. Then as soon as all your URL tasks are done, it will call your done operation, which you can set up to do whatever you want.
You will probably need to subclass Operation so you can update the isExecuting and isFinished properties properly. This question may be of some help here.