Skip to content

Instantly share code, notes, and snippets.

@kean
Last active November 15, 2018 19:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kean/f5ff6412c42398b2bea0d468d3925dc0 to your computer and use it in GitHub Desktop.
Save kean/f5ff6412c42398b2bea0d468d3925dc0 to your computer and use it in GitHub Desktop.
CancellationToken
// MARK: - CancellationTokenSource
/// Manages cancellation tokens and signals them when cancellation is requested.
///
/// All `CancellationTokenSource` methods are thread safe.
final class CancellationTokenSource {
/// Returns `true` if cancellation has been requested.
var isCancelling: Bool {
_lock.lock(); defer { _lock.unlock() }
return _observers == nil
}
/// Creates a new token associated with the source.
var token: CancellationToken {
return CancellationToken(source: self)
}
private var _observers: ContiguousArray<() -> Void>? = []
/// Initializes the `CancellationTokenSource` instance.
init() {}
fileprivate func register(_ closure: @escaping () -> Void) {
if !_register(closure) {
closure()
}
}
private func _register(_ closure: @escaping () -> Void) -> Bool {
_lock.lock(); defer { _lock.unlock() }
_observers?.append(closure)
return _observers != nil
}
/// Communicates a request for cancellation to the managed tokens.
func cancel() {
if let observers = _cancel() {
observers.forEach { $0() }
}
}
private func _cancel() -> ContiguousArray<() -> Void>? {
_lock.lock(); defer { _lock.unlock() }
let observers = _observers
_observers = nil // transition to `isCancelling` state
return observers
}
}
// We use the same lock across different tokens because the design of CTS
// prevents potential issues. For example, closures registered with a token
// are never executed inside a lock.
private let _lock = NSLock()
/// Enables cooperative cancellation of operations.
///
/// You create a cancellation token by instantiating a `CancellationTokenSource`
/// object and calling its `token` property. You then pass the token to any
/// number of threads, tasks, or operations that should receive notice of
/// cancellation. When the owning object calls `cancel()`, the `isCancelling`
/// property on every copy of the cancellation token is set to `true`.
/// The registered objects can respond in whatever manner is appropriate.
///
/// All `CancellationToken` methods are thread safe.
struct CancellationToken {
fileprivate let source: CancellationTokenSource? // no-op when `nil`
/// Returns `true` if cancellation has been requested for this token.
/// Returns `false` if the source was deallocated.
var isCancelling: Bool {
return source?.isCancelling ?? false
}
/// Registers the closure that will be called when the token is canceled.
/// If this token is already cancelled, the closure will be run immediately
/// and synchronously.
func register(_ closure: @escaping () -> Void) {
source?.register(closure)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment