Is there a difference between "@MainActor in" and "MainActor.run"?
Asked Answered
G

1

20

Is there any difference between:

Task { await MainActor.run { ... } }

and

Task { @MainActor in ... }
Gathering answered 22/9, 2022 at 10:40 Comment(1)
Offhand, I would suspect the first one to create a small task that simply "trampolines" to the main actor, while the second transitions to the main actor directly. Some time spent in the debugger might verify that.Halfway
A
14

One difference is that one takes a synchronous closure whereas the other uses an async closure. Specifically, run takes a synchronous closure (i.e., the body is not async):

public static func run<T>(resultType: T.Type = T.self, body: @MainActor @Sendable () throws -> T) async rethrows -> T where T : Sendable

This is ideally suited for the scenario where you are on some other actor, but want to run a series of three methods, all of which are isolated to the main actor, but want to do it with a single context switch, not three.

But, in Task.init, the operation is async, which makes it a slightly more flexible mechanism:

public init(priority: TaskPriority? = nil, operation: @escaping @Sendable () async -> Success)

So, to illustrate the difference, consider:

Task { @MainActor in
    statusText = "Fetching"
    await viewModel.fetchData()
    statusText = "Done"
}

But you cannot await within MainActor.run:

Task {
    await MainActor.run {            // Cannot pass function of type '@Sendable () async -> ()' to parameter expecting synchronous function type
        statusText = "Fetching"
        await viewModel.fetchData()
        statusText = "Done"
    }
}

You would have to insert yet another Task inside. (!)

Task {
    await MainActor.run {
        Task {
            statusText = "Fetching"
            await viewModel.fetchData()
            statusText = "Done"
        }
    }
}

I actually use both patterns sparingly, but this is one difference between them.

Aconcagua answered 11/11, 2022 at 21:16 Comment(1)
FWIW, I find that if the method or property is defined on the correct actor, it largely renders the above patterns moot. The burden should be at the point of definition, not at the call point, IMHO. There are edge cases where these are useful (e.g. to reduce the number of context switches), but often it is just code smell.Aconcagua

© 2022 - 2025 — McMap. All rights reserved.