skip to Main Content

I’m converting my old project from DispatchQueue to async await, there’s a time consuming operation which I wrapped in the global dispatch queue:

DispatchQueue.global().async {
  self.processed = processData(input)
  DispatchQueue.main.async {
    render() // reload UI with self.processed
  }
}
  • processData() is a time consuming synchronous operation
  • render() updates UI with processed data, and it need to be on main thread

Looks like the closest thing to global queue is to use Task.detached, but using it I can’t mutate main actor-isolated properties e.g. self.processed.

Then I thought about doing this:

processData(input: Input) async -> Output {
  await withCheckedContinuation { continuation in
    DispatchQueue.global().async {
      let output = process(input)
      continuation.resume(returning: output)
    }
  }
}
…
let processed = await processData(input)
render()

But it feels like doing it for the sake for using async/await while still use DispatchQueue. Any thoughts? Thanks!

2

Answers


  1. You can use Task with background priority likewise:

    Task.detached(priority: .background) {
      let output = await processData(input:yourInput)
      await MainActor.run {
        render()
      }
    }
    
    func processData(input: Input) async -> Output {
        //do your expensive processing
    }
    
    Login or Signup to reply.
  2. Yes, you theoretically can use a detached task.

    Just make sure that the method which has this code is isolated to the main actor, as well as the render method, but that the processData method is not. E.g., in a type that is, itself, is actor isolated then you just need to mark processData as nonisolated:

    @MainActor
    final class Foo: Sendable {
        var processed: Output?
    
        func processAndUpdateUI(for input: Input) async throws {
            processed = try await Task.detached {
                try self.processData(input)
            }.value
    
            render()
        }
    
        func render() {…}
    
        nonisolated func processData(_ input: Input) throws -> Output {…}
    }
    

    But I would explicitly advise against the MainActor.run {…} or Task { @MainActor in …} patterns. That is brittle. The burden for getting onto the right actor should not fall on the shoulders of the caller. Just make sure that the relevant methods and properties are actor-isolated and the compiler will ensure that you call these methods correctly, that values crossing actor-isolation boundaries are Sendable, etc.

    A note on this latter point. When you pass objects between threads, you have to let the compiler know that they are thread-safe, i.e., Sendable. See WWDC 2022 video Eliminate data races using Swift Concurrency. In Swift 5.x, it will not always warn you if your types are Sendable or not, so consider turning changing the “Strict Concurrency Checking“ build setting to “Complete”:

    enter image description here

    The first time or two you deal with Sendable types, it can seem awfully confusing, but hopefully the above videos will be helpful. But once you master Sendable conformance, it will become second nature and you’ll wonder why you suffered through all those thread-safety headaches of yesteryear.


    The problem is that detached tasks are unstructured concurrency. I.e., if you cancel the parent task, it will not propagate the cancelation to the child tasks.

    So, I might remain within structured concurrency. E.g., I might move processData into its own actor:

    @MainActor
    final class Bar: Sendable {
        private let processor = Processor()
        var processed: Output?
    
        func processAndUpdateUI(for input: Input) async throws {
            processed = try await processor.processData(input)
            render()
        }
    
        func render() {…}
    }
    
    actor Processor {
        func processData(_ input: Input) throws -> Output {
            while … {
                try Task.checkCancellation() // periodically check to make sure this hasn’t been canceled
    
                …
            }
    
            return output
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search