10

Is there any difference between:

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

and

Task { @MainActor in ... }
Philip Pegden
  • 1,732
  • 1
  • 14
  • 33
  • 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. – Scott Thompson Sep 26 '22 at 13:14

1 Answers1

7

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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 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. – Rob Nov 11 '22 at 21:17