Communicating between two concurrent tasks

I am relatively new to Swift. I actually done some Swift few years ago (I have just seen I left a message in this forum 5 years ago...) but I am starting again and happy to see the concurrency features have improved in the recent versions.
I spent quite a lot of hours to look into the documentation (and learning more concept in the meantime), but I cannot see a easy way to communicate between two tasks (or even thread or whatever it runs concurrently in a same process).

The concept is simple and quite common in other languages. I only need a queue that one task (the client) could send object while the other task (the server) would wait for the object to be present.

I looked at Actor, AsyncSequence, AsyncStream. And I tried to prototype it. But always the same issue. How to efficiently make the server to wait for an object to be present in the queue.
I could maybe implement AsyncSequence or AsyncStream and abuse from await Task.yield() and wait for my queue to not be empty. But it does look like to be the most efficient.

Using NSCondition.wait() / NSCondition.signal() seems to be the optimal solution but is it adapter to async/await concurrency as it is thread related.

There was already this similar thread Communicating between two concurrent tasks . The advise was to use AsyncStream

I wrote the code below but I am not sure how to get the data from read()...

class DataPipe {
    var buffer: [UInt8]
    var continuation: AsyncStream<[UInt8]>.Continuation!

    init() {
        buffer = [UInt8]()
    }

    func send(_ data: [UInt8]) {
        self.buffer.append(contentsOf: data)

        if (self.continuation != nil) {
            self.continuation.yield(self.buffer)
            self.buffer.removeAll()
        }
    }

    func read() -> AsyncStream<[UInt8]> {
        AsyncStream { continuation in
            continuation.onTermination = { _ in
                self.continuation = nil
            }

            if (self.buffer.isEmpty) {
                self.continuation = continuation
            } else {
                let copiedData = [UInt8](self.buffer)
                self.buffer.removeAll()
                continuation.yield(copiedData)
            }
        }
    }
}

var serverDataPipe = DataPipe()

func client() async {
    repeat {
        print("Client send data to server")
        await serverDataPipe.send([1, 2])
        try! await Task.sleep(for: .seconds(1))
    } while true
}

func server() async {    
    repeat {
        print("Server waits for data")
        let data = await serverDataPipe.read()
        print("Server received \(data)")
        try! await Task.sleep(for: .seconds(2))
    } while true
}

async let r1: Void = client()
async let r2: Void = server()
_ = await [r1, r2]

Here is what I see in my terminal:

Client send data to server
Server waits for data
Server received AsyncStream<Array<UInt8>>(context: Swift.AsyncStream<Swift.Array<Swift.UInt8>>._Context)
Client send data to server
Client send data to server
Server waits for data
Server received AsyncStream<Array<UInt8>>(context: Swift.AsyncStream<Swift.Array<Swift.UInt8>>._Context)
Client send data to server
(...)

On Python, it exists asyncio queue (Queues — Python 3.12.0 documentation) and queue (" A synchronized queue class") queue — A synchronized queue class — Python 3.12.0 documentation
But I have not found so far such structure in Swift.