Best way to run an anonymous function on the main actor

Now that we have the new concurrency features in Swift 5.5, I'm wondering what the best way is to perform a small bit of work on the main actor from an asynchronous function. I know I can create a dedicated function and decorate it with @MainActor, but sometimes the work is too trivial to get a named function.

In the past, I would do this with GCD, e.g.

DispatchQueue.main.async { print("Okay, we're done")

Is that still the recommended way to run small, anonymous functions on the main actor? Or is there a newer way, less tied to GCD?

From the Closures section in SE-0316, I guess you can do

await { @MainActor in print("Okay, we're done") }()

or

Task { @MainActor in print("Okay, we're done") }
Task.detached { @MainActor in print("Okay, we're done") }
3 Likes

You can call MainActor.run directly:

await MainActor.run {
  print("Okay, we're done")
}
6 Likes

That’ll suspend the calling function and resume it once the main actor is done which mightn’t be necessary/possibly wasteful. What if you just simply want to “fire and forget” some work off to the Main actor?

The only way to “fire and forget” are unstructured tasks. This is on purpose, we try to dissuade from using fire and forget most of the time.

Having that said, it is:

Task { @MainActor in hello() }
// or better 
@MainActor func someFunctionOnMainActor() {}
Task { await someFunctionOnMainActor() }
2 Likes

Hi @ktoso, I recently learned about the Task { @MainActor in hello() } syntax and I'm wondering why you suggest the alternative (the // or better bit) instead?

Is it because a Task can call out to functions on various actors without needing to define everything as occurring on the main actor? What happens if you mark the Task with @MainActor like that but then call out to function on another actor?

I find it hard to build a strong intuition about all this, especially because (from my understanding) the code of two actors may even be running on the same thread (?)

You’d get unnecessary hops.

No. No actors ever use the same thread concurrently, that’s not a thing. They may (not the main one) use some concrete thread one after another tho… it is best to not really think about threads but just executors instead.

Yes thanks for clarifying. I didn't mean the code from two actors running concurrently on the same thread, but potentially serially, from what I understand.

So from what I can tell the main reason you're suggesting against Task { @MainActor in would be to potentially reduce hops if you also want to call into non-MainActor async code within that Task. Am I understanding that correctly?

If you're spawning a task to immediately call other actor methods yeah, I'd suggest not instructing the task to be run on any specific actor, since that'd needlessly hop to that one, and then off from it again.

I don't think we optimize such things away.

1 Like