Rakshith Nandish
Rakshith Nandish

Reputation: 639

MainActor in Task

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

Answers (2)

timbre timbre
timbre timbre

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

shim
shim

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

Related Questions