Swift Call async function on main actor from synchronous context

Viewed 414

I am trying to understand as to why following piece of code throws an assertion. What I am trying to do is to call asyncFunc() on main thread/main actor from call site. I don't want to decorate asyncFunc with @MainActor as I want the function to be thread agnostic.

func asyncFunc() async -> String? {
     dispatchPrecondition(condition: .onQueue(.main))
     return "abc"
}

func callSite() {
     Task { @MainActor in
          await asyncFunc()
     }
}

My understanding was that Task { @MainActor ...} would execute all the following code on MainActor/main thread.

1 Answers

Currently actors in swift are reentrant as mentioned in proposal:

Actor-isolated functions are reentrant. When an actor-isolated function suspends, reentrancy allows other work to execute on the actor before the original actor-isolated function resumes, which we refer to as interleaving. Reentrancy eliminates a source of deadlocks, where two actors depend on each other, can improve overall performance by not unnecessarily blocking work on actors, and offers opportunities for better scheduling of (e.g.) higher-priority tasks.

Hence, your asyncFunc is not run on MainActor as it will block the actor from processing other high-priority tasks. To fix this you can try following two approaches:

  1. Make your function synchronous, so that it will run on MainActor:
func syncFunc() -> String? {
     dispatchPrecondition(condition: .onQueue(.main))
     return "abc"
}

func callSite() {
     Task { @MainActor in
         syncFunc()
     }
}
  1. Or explicitly mark your asynchronous function to be run on MainActor:
@MainActor
func asyncFunc() async -> String? {
     dispatchPrecondition(condition: .onQueue(.main))
     return "abc"
}

func callSite() {
     Task {
          await asyncFunc()
     }
}

Note that same reentrancy rule applies here as well, synchronous calls in asyncFunc (i.e. dispatchPrecondition) will be executed on MainActor while any asynchronous call inside asyncFunc will not be called on MainActor.

Related