Last active
October 24, 2025 11:39
-
-
Save macguru/d39e78a9a7f3c5a9ba742a6759d700c5 to your computer and use it in GitHub Desktop.
ScopedTask: A wrapper around Task that auto-cancels when going out of scope.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// A task that is cancelled if goes out of scope, i.e. the last reference on it has been discarded. | |
| public final class ScopedTask<Success: Sendable, Failure: Error>: Sendable { | |
| /// The underlying task | |
| let wrapped: Task<Success, Failure> | |
| /// Wraps a task into a scoped task. | |
| init(wrapping task: Task<Success, Failure>) { | |
| wrapped = task | |
| } | |
| /// Auto-cancellation on deinit | |
| deinit { | |
| wrapped.cancel() | |
| } | |
| /// Cancels the wrapped task. | |
| public func cancel() { | |
| wrapped.cancel() | |
| } | |
| /// The result or error from the task, after it completes. | |
| public var result: Result<Success, Failure> { | |
| get async { | |
| await wrapped.result | |
| } | |
| } | |
| /// A Boolean value that indicates whether the task should stop executing. | |
| public var isCancelled: Bool { | |
| wrapped.isCancelled | |
| } | |
| } | |
| public extension ScopedTask where Failure == any Error { | |
| /// Initializes a scoped task with a throwing closure | |
| convenience init( | |
| name: String? = nil, | |
| @_inheritActorContext _ operation: sending @escaping @isolated(any) () async throws -> Success | |
| ) { | |
| self.init(wrapping: Task(name: name, operation: operation)) | |
| } | |
| /// The result from a throwing task, after it completes. | |
| var value: Success { | |
| get async throws { | |
| try await result.get() | |
| } | |
| } | |
| } | |
| public extension ScopedTask where Failure == Never { | |
| /// Initializes a scoped task with a non-throwing closure | |
| convenience init( | |
| name: String? = nil, | |
| @_inheritActorContext _ operation: sending @escaping @isolated(any) () async -> Success | |
| ) { | |
| self.init(wrapping: Task(name: name, operation: operation)) | |
| } | |
| /// The result from a non-throwing task, after it completes. | |
| var value: Success { | |
| get async { | |
| await result.get() | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Yeah, wrapping
Taskcreation, in a synchronous function especially, is so hard to do.@_inheritActorContextis not the same as an isolated parameter. And while that attribute is considered unsupported, it is not (yet anyways) deprecated because there is no other way to emulate its behavior.However, in this very specific situation, a side-effect of using an isolated parameter should end up causing this to work the same.