@crontab That was spot on. Thanks.
If it helps @ibex10, I can provide more concrete examples.
Case 1
Assume that I am writing library that encapsulates the communication to a Bluetooth peripheral. As I've outlined before, the requirements are that before I send subsequent requests I need to wait for an acknowledgement from the peripheral.
func enqueue(_ data: Data) async throws {
// This is synchronous but theoretically I could even use .withResponse and wait for a callback from peripheral(_:didWriteValueFor:error:)
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
// However in this scenario I am just waiting for peripheral(_:didUpdateValueFor:error:) which I converted to an AsyncStream
for try await response in responseStream {
if /* is correct acknowledgement for request */ {
break
}
}
}
I now need to ensure, that if enqueue
is called multiple times from multiple tasks, it still waits for the previous acknowledgement before the next peripheral.writeValue(_:for:type:)
.
I solved this with AsyncSemaphore
by @gwendal.roue by doing this:
let bluetoothSemaphore = AsyncSemaphore(value: 1)
func enqueue(_ data: Data) async throws {
await bluetoothSemaphore.wait()
defer { bluetoothSemaphore.signal() }
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
for try await response in responseStream {
if /* is correct acknowledgement for request */ {
break
}
}
}
Case 2
Kind of a similar use case now that I am thinking about it. However this time, it was a device I was communicating with over local network. This time the requirement was to have a cooldown between sending commands.
func enqueue(_ data: Data) async throws {
// This is synchronous but theoretically I could even use .contentProcessed and wait for its callback
connection.send(content: data, completion: .idempotent)
// Let the remote device cooldown by waiting here
try await Task.sleep(...)
}
Same solution here with AsyncSemaphore
.
Conclusion
Again, I am not saying that this kind of API requires primitives like semaphores. I do see and agree with the point of @ktoso being made here Using semaphores in actor code - #40 by ktoso. And I do think this can be solved with actors, if they would support some kind of non-reentrancy.
At the time of implementing those features the best solution was (and I still think it is) AsyncSemaphore
.