Why don't actors and DispatchSemaphores work together?
I have discovered this when I was trying out the following code originally posted by @nikolai.ruhe
actor MeetingPoint {
var completion: CheckedContinuation<Void, Never>? = nil
func join() async {
if let completion = completion {
self.completion = nil
completion.resume()
} else {
await withCheckedContinuation {
self.completion = $0
}
}
}
}
I first, naively, used an actor and a DispatchSemaphore to create an EventFlag
.
actor EventFlag
func log (_ s: String) {
print ("Using an actor: \(s)")
}
actor EventFlag {
private var max = 0
private var value = 0
private var semaphore = DispatchSemaphore (value: 0)
init (max: Int) {
self.max = max
}
func set () {
value += 1
if value >= max {
semaphore.signal()
}
}
func wait () {
if value < max {
semaphore.wait()
}
}
}
The driver:
let mp = MeetingPoint ()
let ef = EventFlag (max: 2)
Task {
await mp.join ()
print ("joined 1")
await ef.set()
}
Task {
await mp.join ()
print ("joined 2")
await ef.set()
}
log ("wait...")
await ef.wait ()
log ("done.")
The output:
Using an actor: wait...
joined 2
joined 1
From the output, I can tell that my actor EventFlag
does not work as intended.
However, if I use a class instead, I get the desired result.
class EventFlag
func log (_ s: String) {
print ("Using a class instead: \(s)")
}
class EventFlag {
private var max = 0
private var value = 0
private var lock = DispatchSemaphore (value: 1)
private var flag = DispatchSemaphore (value: 0)
init (max: Int) {
self.max = max
}
func set () async {
lock.wait()
value += 1
if value >= max {
flag.signal()
}
lock.signal()
}
func wait () async {
flag.wait()
}
}
The output:
Using a class instead: wait...
joined 2
joined 1
Using a class instead: done.
Obviously I am doing something wrong in my actor EventFlag
, but I can't figure it out.
Could anyone help me understand what I am doing wrong?
Update:
The following version with CheckedContinuation seems to work, but I am not sure if it is correct.
actor EventFlag
actor EventFlag {
private var max = 0
private var value = 0
private var done = false
private var completion: CheckedContinuation <Void, Never>? = nil
init (max: Int) {
self.max = max
}
func set () {
value += 1
if value >= max {
done = true;
if let completion {
completion.resume()
}
}
}
func wait () async {
if !done {
if value < max {
await withCheckedContinuation {
self.completion = $0
}
}
}
}
}