2

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.

Shape Fit
  • 33
  • 1
  • @Rob For example, let's say I sent a string to the list with AsyncChannel. With for await I can check this list once. When I want to check later, the values ​​do not appear. am I wrong? – Pehr Sibusiso Aug 20 '23 at 21:21
  • @Rob I may not have figured out how to implement this. Any chance to show an example? Also is HangarRash right when they say it? – Pehr Sibusiso Aug 20 '23 at 21:23
  • Watch Meet “async/await” it’s pretty linear – lorem ipsum Aug 20 '23 at 21:32
  • You can check DispatchSemaphore. – EsatGozcu Aug 21 '23 at 01:39
  • No, never use semaphores to manage dependencies in Swift concurrency. In GCD, it is a bad idea, but “…primitives like semaphores and condition variables are unsafe to use with Swift concurrency.” https://developer.apple.com/videos/play/wwdc2021/10254/?time=1558 – Rob Aug 21 '23 at 03:48

1 Answers1

2

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:

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • I think calling the two `example` methods one after the other in the same scope would be closer to the code in the question. – JeremyP Aug 21 '23 at 08:37
  • @JeremyP – Agreed. But the whole async-write and sync-read is a synchronization mechanism, so it’s a bit silly to do that from the same context anyway, so I assume the OP simplified his example when posting his question. But revised my answer, regardless. – Rob Aug 21 '23 at 16:16