Using `async` functions from synchronous functions (and breaking all the rules)

How about not using actors? e.g. serialising to a private queue:

class SharedData {
  private var value = 0
  private var queue = DispatchQueue(label: "SharedData_Private")

  func setValue(_ newValue: Int) {
    queue.sync { self.value = newValue }
  }

  func setValue(_ newValue: Int) async {
    await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
      queue.async {
        self.value = newValue
        continuation.resume()
      }
    }
  }
}

This pattern would ensure that synchronous calls are serialised on a new, private thread (not from the global pool), while async callers who are on pooled threads can yield those execution resources while they wait (rather than block).

It's a bit ugly - it's basically implementing your own actor, but it at least gives you the control you need to ship an async API without a new major version and without resorting to private interfaces. Eventually you could replace it with the language's provided actors.

4 Likes