Search code examples
swiftgrand-central-dispatchswift-concurrency

Swift Concurrency - Sync and Async Task


Using GCD, I am able to run a synchronous task after an asynchronous task has finished.

let queue = DispatchQueue(label: "for.test")
var exampleList = [String]()

queue.async {
  exampleList.append("Hello")
}

queue.sync {
  for item in exampleList {
     if item == "Hello" {
        print("list contains hello")
     }
  }
}

// list contains hello

I couldn't find how to do this with Swift Concurrency. How can I guarantee that one task will run after another?

I tried using AsyncChannel. It doesn't quite work as it consumes events one time.


Solution

  • It is probably best if you do not look for literal translations of your GCD patterns, but rather focus on the broader problem you were trying to solve, and find a comparable pattern in Swift concurrency.

    For example, this pattern of asynchronous writes and synchronous reads is a common “synchronization” technique, providing thread-safe access to some mutable state. We see this in the reader-writer pattern with concurrent queue with barrier for writes, but we also use this pattern when synchronizing with serial queues, like shown in your question.

    If that is the intent, the equivalent Swift concurrency mechanism for protecting a shared mutable state would be an actor.

    E.g.,

    actor Example {
        var list: [String] = []
        
        func append(_ string: String) {
            list.append(string)
        }
        
        func contains(_ string: String) -> Bool {
            list.contains(string)
        }
    }
    

    Then you can do things like:

    let example = Example()
    
    func addSomeValues() async {
        await example.append("Hello")
    }
    
    func checkValues() async {
        if await example.contains("Hello") {
            print("list contains hello")    
        }
    }
    

    Perhaps needless to say, you can also do:

    let example = Example()
    
    await example.append("Hello")
    
    if await example.contains("Hello") {
        print("list contains hello")    
    }
    

    (Then again, if you were appending and reading within the same context, using Swift concurrency would be unnecessary. But the same stands for the serial dispatch queue in your original example, too. This sort of synchronization only has value when interacting with this shared mutable state from different contexts.)

    So, it comes down to what problem you are attempting to solve with this GCD code. If synchronization, consider an actor. If you really need serial behaviors, perhaps see Swift 5.6 how to put async Task into a queue.


    References: