I am using a semaphore and a Task to hibernate, that is, to pause the execution, in the non-async send function below. I do this because sending takes variable time in real life.

actor DeviceProxy {
    var state = State ()
    
    // non-reentrant, there are no suspension points
    func enqueue (seq: Int msg: Message) {
        let pstate = state
        send (msg)
        state.inspect (seq, pstate)
        assert (pstate == state)
        state.counter += 1
    }
    
    // Simulate sending
    // non-reentrant, there are no suspension points
    func send (_ msg: Message) {
        // simulate real-life, sending takes variable time
        let sem = DispatchSemaphore (value: 0)
        
        Task {
            let range = 0...7
            let s = Int.random (in: range)
            await hibernate (seconds: s)
            sem.signal()
        }
        sem.wait()
    }
}

func hibernate (seconds: Int) async {
    let s = UInt64 (1_000_000_000 * seconds)
    try! await Task.sleep (nanoseconds: s)
}

struct State {
    var counter = 0
    
    func inspect (_ seq: Int, _ before: State) {
        let ss = String (format: "%2d", seq)
        let bs = String (format: "%2d", before.counter)
        let cs = String (format: "%2d", counter)
        print (ss, "state: before", bs, "after", cs, before.counter != counter ? "changed" : "")
    }
    
    static func == (_ u: State, _ v: State) -> Bool {
        u.counter == v.counter
    }
}

I have a feeling that this is not the right way to go. How would this be done correctly?

If that's a test code, Thread.sleep might be fine, as I understand that's the intention anyway. Less intrusive than a semaphore at least.

Got it, thank you!