Swift Concurrency 'gate' type to control forward progress of async tasks.
A gate can be opened and closed, and while closed it cannot be entered. Async tasks can pass through the gate at appropriate point(s) in their execution (chosen by them, like checking for task cancellation). If the gate is closed at the time they try to enter it, they will pause and wait for it to open before proceeding.
The gate can be opened or closed from any code (not just async contexts).
An example use is in SwiftUI where you have background task(s) (e.g. from the task
view modifier) that you want to pause & resume in response to state changes:
struct Example: View {
let frames: NSImage
@Binding var animate: Bool {
didSet {
guard animate != oldValue else { return }
if animate {
self.animationGate.open()
} else {
self.animationGate.close()
}
}
}
@State private var animationGate = Gate(initiallyOpen: true)
@State var currentFrameIndex: Int = 0
var body: some View {
Image(nsImage: self.frames[self.currentFrameIndex])
.task {
while let _ = try? await self.animationGate.enter() {
self.currentFrameIndex = (self.currentFrameIndex + 1) % self.frames.count
try? await Task.sleep(for: .seconds(1) / 60)
}
}
}
}
Note that enter
will always throw if the task is cancelled, so you don't need to manually check for cancellation (e.g. Task.checkCancellation
/ Task.isCancelled
) if you are using Gate
entry like in the above example.
This was inspired by (and based in part on) Gwendal Roué's Semaphore
package.