4 Comments

Unstructured tasks inherit the actor context they’re created in. In your example, since the task is created inside a UIViewController, it runs on the MainActor, meaning any code in the task will be executed on the main thread.

Apple covers this in WWDC 2021 Session 10134 - https://developer.apple.com/videos/play/wwdc2021/10134/?time=1273.

If you don’t want a task to inherit the actor context, you can use Task.detached. Just be careful—this can lead to issues like running UIKit code on a background thread, which isn’t safe.

Expand full comment

In your example of the 2 prints on main, I assume the first comes from a method (or class) that is MainActor bound? If so, can’t you still use Task { someMainActorBoundEntity.syncMethod() } and this will be guaranteed to run on main? Is it the fact that the only enclosed content is a print statement mean an optimization is made to not require main execution?

Expand full comment
author

So the method isn't actually main-actor bound at all, but it's originally called by onAppear in a SwiftUI view up the call stack which of course lives on the main actor.

That's definitely right, any main-actor bound entity will run sync functions on the main thread/actor. And even if not using an entity, you can use await MainActor.run { }

I never considered the fact that it's a print statement really, but you might be completely right about optimisations - however, the behaviour of Swift Concurrency can also be somewhat non-deterministic in edge cases - there's a great article by the Ollie app explaining some of the gotchas they experienced - https://medium.com/goodones/why-ollie-is-moving-away-from-swiftui-to-uikit-cfdefe918d1c

(Great comment btw, it made me think really hard!)

Expand full comment

Isolated (any) feature will affect where the task starts.

Quote from Matt

“But Task has to start somewhere, so it first begins with a global executor context. And then the very next thing that happens is the closure hops over to the correct actor. This double hop, aside from being inefficient, has a very significant programmer-visible effect: Task does not preserve order.”

This unnecessary hope is solved by se 0431 (https://github.com/swiftlang/swift-evolution/blob/main/proposals/0431-isolated-any-functions.md)

https://www.massicotte.org/concurrency-swift-6-se-0431

So in swift 6 mode if task is created from MainActor it will absolutely starts on Main Actor

Expand full comment