Run a @MainActor closure synchronously

Hello,

When you have a closure marked @MainActor, is there a way to explicitly say to Swift, "I know I am on the main thread, please run this closure synchronously here" (possibly aborting if not on the main thread)?

In a library like RxSwift, some methods guarantee that the closure you pass to them will be run on the main thread. So you would like to mark the closures given to such methods @MainActor.

Such a library is using its own threading infrastructure, so there is no way for Swift to statically know if some code will run on the main thread or not. Currently if you try calling a @MainActor closure normally in such a context you'll get an warning like the following:

warning: call to main actor-isolated parameter 'xxxxx' in a synchronous nonisolated context; this is an error in Swift 6

It seems having a way to say to Swift trust me I am running on the main actor would help adopting more Swift Concurrency features in existing code bases.

Thanks

1 Like

Not sure this is what you have in mind, but there is a @MainActor(unsafe) attribute. As far as I know, it tells the compiler to disable the checks.

SwiftUI uses this in a few places, e.g. for the View.body property, which is declared like this:

public protocol View {
  …
  @SwiftUI.ViewBuilder @_Concurrency.MainActor(unsafe)
  var body: Self.Body { get }
}

Maybe something like the following would work. (Note: I only tested this very briefly, and only in a playground. It may be wrong. But it does run and suppresses the MainActor-related warning.)

extension MainActor {
  @MainActor(unsafe)
  static func runUnsafe<R>(_ body: @MainActor () -> R) -> R {
    body()
  }
}

// MARK: - Example

import Combine
import Foundation

@MainActor
func processOnMainActor(_ data: Data) -> Data {
  print(#function, data)
  return data
}

let subscription = URLSession.shared.dataTaskPublisher(for: URL(string: "https://placekitten.com/500/500")!)
  .assertNoFailure()
  .receive(on: DispatchQueue.main)
  .map { data, _ in
    MainActor.runUnsafe { processOnMainActor(data) }
  }
  .sink { value in
    print("sink", value)
  }

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true
2 Likes

@MainActor(unsafe) looks to be what I was searching for, thanks a lot!
I'll try playing with it.

1 Like

FWIW, it seems that swift-evolution/0392-custom-actor-executors.md at main · apple/swift-evolution · GitHub tackles that problem.