Reputation: 639
Hi I have been trying to wrap my head around the usage @MainActor in a Task, as part of Swift Concurrency. If I have a code snippet like the one below:
Task { @MainActor in
do {
let imageData = try await fetchImage?.value
// Update UI here, assign to an image
} catch {
self.image = .error(error)
}
}
where fetchImage is an async task and I am interested in the value property. From what I understand, as this task is marked @MainActor, it has the context of the MainActor. So when fetchImage is called, does the call happen on the main thread itself? or is that the call to fetchImage happens on a background thread, it only switches back to main thread once the call returns?
Upvotes: 3
Views: 1978
Reputation: 13996
TL;DR: Unless you explicitely designated the property (or its owning class/actor) to run on main thread, execution of await fetchImage?.value
is not guaranteed to run on main thread.
Long answer.
So lets have a test framework slightly simplified:
class Test {
var fetchImage = //... TBD
func f() {
Task { @MainActor in
print("f is running on", Thread.current) // we will ignore a warning on this line for test purpose
let _ = await fetchImage.value
}
}
}
let t = Test()
t.f()
So what are the cases where you need to await for property value:
Case 1: property is actually doing some async work on get:
class ImageFetcherAsync {
var value: String {
get async {
print("async's value is running on", Thread.current)
return "good bye"
}
}
}
class Test {
var fetchImage = ImageFetcherAsync()
...
Nothing here forces property get
to run on main thread, and so the result it:
f is running on <_NSMainThread: 0x600001704040>{number = 1, name = main}
async's value is running on <NSThread: 0x60000171c080>{number = 7, name = (null)}
Case 2: your property is not async, but it's part of an actor (which runs on isolated context):
actor ImageFetcherActor {
var value: String {
print("actor's value is running on", Thread.current)
return "hello"
}
}
class Test {
var fetchImage = ImageFetcherActor()
...
Yet again, the property won't be running on main thread (why would it - nothing tells it to):
f is running on <_NSMainThread: 0x60000170c000>{number = 1, name = main}
actor's value is running on <NSThread: 0x60000170e180>{number = 7, name = (null)}
How to make it run on main thread? Simplest is add @MainActor
to it in both cases:
@MainActor var value: String {
...
Then of course it will be running on main thread in both cases. In first case you will need await
still, but in second case the property will be retrieved synchronously:
actor ImageFetcherActor {
@MainActor var value: String {
...
var fetchImage = ImageFetcherActor()
...
let _ = fetchImage.value // not async
Upvotes: 4
Reputation: 10146
It somewhat depends on what fetchImage
is. Should be fairly easy to put a breakpoint inside fetchImage
and call po Thread.current
though so you can see definitively.
The body of the Task
will be run on the main thread but when it calls out to async methods they may or may not run on the main thread depending on whether they are also marked as @MainActor
or not.
There's a good related StackOverflow answer here.
async methods do not “inherit” the actor of their caller
Upvotes: 5