If you want the function to stay on whatever actor it was called on, you can use the nonisolated keyword.
Confusion
However based on my testing the nonisolated async function doesn't stay on the same actor (main actor).
nonisolatedasync function compute() doesn't run on the main thread (because MainActor.assertIsolated("Not isolated") causes a crash).
Am I missing something?
Questions:
Based on my interpretation what was stated in the video I was thinking that if already on the main actor and a non-isolated async function is invoked it would continue to run the main actor, but it doesn't seem to be the case. Am I missing something?
How to know if a async function is running on main thread or background thread?
Is it based on MainActor.assertIsolated("Not isolated")?
Project
Environment:
Xcode26.0 beta (17A5241e)
Toolchain: Swift 6.2 Development Snapshot 2025-06-14 (a)
Build Setting
Default Actor Isolation: Main Actor
Swift language version: 6
Code
Since the Default actor isolation is Main Actor, all run on Main Actor by default.
import Foundation
import Combine
// My assumption this would be Main actor because Default Actor Isolation: is Main Actor
@Observable
class Service {
// I thought this would also be Main actor based on what is mentioned in the video
// I am just saying based on by calling `MainActor.assertIsolated("Not isolated")`
nonisolated
func compute() async {
// MainActor.assertIsolated("Not isolated")
// When MainActor.assertIsolated is uncommented would crash implying
// it is not running on the main thread
for index in 0..<1000000 {
print("index: \(index)")
}
}
}
This mode will eventually become the new default mode, but for now it is opt-in. We think this behavior is what nonisolated async functions always should have been doing, but we need to be careful about rolling out the behavior change, thus staging it in this way.
@ktoso Thanks a lot!!! I was breaking my head over it.
I have a few questions:
In future once this feature goes live I suppose only we don't need to mention nonisolated(nonsending) instead could only mention nonisolated? For now we need to mention as nonisolated(nonsending)?
So is the following correct?
@concurrent will change it to background
nonisolated(nonsending) will continue on the same as the actor's executor
@MainActor will change it to main actor.
Actually seems simpler to understand concurrency with the above 3 rules. Not sure if I over simplified them and missed any cases.
Does that mean in different versions of Swift things would behave differently?
Well the "NonisolatedNonsendingByDefault" upcoming feature "is live" in the sense that you can opt into it.
There might be a future in which we make this the default, but it would be tied to a language mode. There's no concrete promise so far when or if that will be flipped to be the default though. But yeah that's the general direction.
this means "hop off calling actor executor; if there is a task executor, use that; if not, use the default global concurrent executor".
yes
yes
Yeah; so that's the "future state of the world". Until then we sadly have to deal with nonisolated and nonisolated(nonsending) but the latter will disapear in use in the long term as people enable the upcoming feature IMHO.
Thanks a lot @bjhomer, turning on "Approachable Concurrency automatically sets all of the flags under "Swift Compiler Upcoming features" which includes nonisolated (nonsending) by default
The video mentions it at the end but didn't realize it.
Thanks a lot @Douglas_Gregor for this video, really helped me understand concurrency and made it more accessible to me.
Thanks a lot to the Swift community for these new Swift concurrency features makes it so much better!!