From 7102a404c21ab2627f8482320bd3600fb4e0c6fa Mon Sep 17 00:00:00 2001 From: Nikita Vasilev Date: Tue, 28 Jan 2025 18:42:59 +0100 Subject: [PATCH] Implement `TaskFactory` (#3) --- .../TaskFactory/ITaskFactory.swift | 105 ++++++++++++++++++ .../Concurrency/TaskFactory/TaskFactory.swift | 42 +++++++ Sources/TestConcurrency/Task.swift | 21 ++++ Sources/TestConcurrency/TestTaskFactory.swift | 83 ++++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 Sources/Concurrency/TaskFactory/ITaskFactory.swift create mode 100644 Sources/Concurrency/TaskFactory/TaskFactory.swift create mode 100644 Sources/TestConcurrency/Task.swift create mode 100644 Sources/TestConcurrency/TestTaskFactory.swift diff --git a/Sources/Concurrency/TaskFactory/ITaskFactory.swift b/Sources/Concurrency/TaskFactory/ITaskFactory.swift new file mode 100644 index 0000000..1336d23 --- /dev/null +++ b/Sources/Concurrency/TaskFactory/ITaskFactory.swift @@ -0,0 +1,105 @@ +// +// Concurrency +// Copyright © 2025 Space Code. All rights reserved. +// + +import Foundation + +// MARK: - ITaskFactory + +// swiftlint:disable attributes +/// A protocol for creating and managing tasks with different configurations. +/// Provides methods to create tasks tied to the current actor context or detached tasks +/// that run independently of the current actor. +public protocol ITaskFactory { + /// Creates a task tied to the current actor context that can throw errors. + /// - Parameters: + /// - priority: The priority of the task (optional). + /// - operation: An asynchronous operation to execute within the task. The operation + /// inherits the current actor context and is isolated to that actor. + /// - Returns: A `Task` object that wraps the result or error of the operation. + func task( + priority: TaskPriority?, + @_inheritActorContext operation: sending @escaping @isolated(any) () async throws -> Success + ) -> Task + + /// Creates a task tied to the current actor context that does not throw errors. + /// - Parameters: + /// - priority: The priority of the task (optional). + /// - operation: An asynchronous operation to execute within the task. The operation + /// inherits the current actor context and is isolated to that actor. + /// - Returns: A `Task` object that wraps the result of the operation. + func task( + priority: TaskPriority?, + @_inheritActorContext operation: sending @escaping @isolated(any) () async -> Success + ) -> Task + + /// Creates a detached task that runs independently of the current actor context + /// and can throw errors. + /// - Parameters: + /// - priority: The priority of the task (optional). + /// - operation: An asynchronous operation to execute within the task. The operation + /// is isolated and does not inherit the current actor context. + /// - Returns: A `Task` object that wraps the result or error of the operation. + func detached( + priority: TaskPriority?, + @_inheritActorContext operation: sending @escaping @isolated(any) () async throws -> Success + ) -> Task + + /// Creates a detached task that runs independently of the current actor context + /// and does not throw errors. + /// - Parameters: + /// - priority: The priority of the task (optional). + /// - operation: An asynchronous operation to execute within the task. The operation + /// is isolated and does not inherit the current actor context. + /// - Returns: A `Task` object that wraps the result of the operation. + func detached( + priority: TaskPriority?, + @_inheritActorContext operation: sending @escaping @isolated(any) () async -> Success + ) -> Task +} + +/// Default implementations for the `ITaskFactory` protocol. +public extension ITaskFactory { + /// Creates a task tied to the current actor context with a default priority + /// that can throw errors. + /// - Parameter operation: An asynchronous operation to execute within the task. + /// - Returns: A `Task` object that wraps the result or error of the operation. + func task( + @_inheritActorContext operation: sending @escaping @isolated(any) () async throws -> Success + ) -> Task { + task(priority: nil, operation: operation) + } + + /// Creates a task tied to the current actor context with a default priority + /// that does not throw errors. + /// - Parameter operation: An asynchronous operation to execute within the task. + /// - Returns: A `Task` object that wraps the result of the operation. + func task( + @_inheritActorContext operation: sending @escaping @isolated(any) () async -> Success + ) -> Task { + task(priority: nil, operation: operation) + } + + /// Creates a detached task with a default priority that runs independently + /// of the current actor context and can throw errors. + /// - Parameter operation: An asynchronous operation to execute within the task. + /// - Returns: A `Task` object that wraps the result or error of the operation. + func detached( + @_inheritActorContext operation: sending @escaping @isolated(any) () async throws -> Success + ) -> Task { + detached(priority: nil, operation: operation) + } + + /// Creates a detached task with a default priority that runs independently + /// of the current actor context and does not throw errors. + /// - Parameter operation: An asynchronous operation to execute within the task. + /// - Returns: A `Task` object that wraps the result of the operation. + func detached( + @_inheritActorContext operation: sending @escaping @isolated(any) () async -> Success + ) -> Task { + detached(priority: nil, operation: operation) + } +} + +// swiftlint:enable attributes diff --git a/Sources/Concurrency/TaskFactory/TaskFactory.swift b/Sources/Concurrency/TaskFactory/TaskFactory.swift new file mode 100644 index 0000000..1fd67a9 --- /dev/null +++ b/Sources/Concurrency/TaskFactory/TaskFactory.swift @@ -0,0 +1,42 @@ +// +// Concurrency +// Copyright © 2025 Space Code. All rights reserved. +// + +public final class TaskFactory: ITaskFactory { + // MARK: Initialization + + public init() {} + + // MARK: ITaskFactory + + // swiftlint:disable attributes + public func task( + priority: TaskPriority?, + @_inheritActorContext operation: sending @escaping @isolated(any) () async throws -> Success + ) -> Task { + Task(priority: priority, operation: operation) + } + + public func task( + priority: TaskPriority?, + @_inheritActorContext operation: sending @escaping @isolated(any) () async -> Success + ) -> Task { + Task(priority: priority, operation: operation) + } + + public func detached( + priority: TaskPriority?, + @_inheritActorContext operation: sending @escaping @isolated(any) () async throws -> Success + ) -> Task { + Task.detached(priority: priority, operation: operation) + } + + public func detached( + priority: TaskPriority?, + @_inheritActorContext operation: sending @escaping @isolated(any) () async -> Success + ) -> Task { + Task.detached(priority: priority, operation: operation) + } + // swiftlint:enable attributes +} diff --git a/Sources/TestConcurrency/Task.swift b/Sources/TestConcurrency/Task.swift new file mode 100644 index 0000000..da85194 --- /dev/null +++ b/Sources/TestConcurrency/Task.swift @@ -0,0 +1,21 @@ +// +// Concurrency +// Copyright © 2025 Space Code. All rights reserved. +// + +// MARK: - ITask + +/// A protocol representing a task that can be awaited until its execution completes. +protocol ITask { + /// Waits for the `Task` to complete by retrieving its result. + func wait() async +} + +// MARK: - Task + ITask + +/// Extends the `Task` type to conform to the `ITask` protocol. +extension Task: ITask { + func wait() async { + _ = await result + } +} diff --git a/Sources/TestConcurrency/TestTaskFactory.swift b/Sources/TestConcurrency/TestTaskFactory.swift new file mode 100644 index 0000000..8bc334f --- /dev/null +++ b/Sources/TestConcurrency/TestTaskFactory.swift @@ -0,0 +1,83 @@ +// +// Concurrency +// Copyright © 2025 Space Code. All rights reserved. +// + +import Concurrency +import Foundation + +// MARK: - TestTaskFactory + +public final class TestTaskFactory: @unchecked Sendable { + // MARK: Properties + + private let lock = NSLock() + private var tasks: [ITask] = [] + + // MARK: Intialization + + public init() {} + + // MARK: Public + + /// Waits until all tasks in the queue have completed execution. + public func waitUntilIdle() async { + while let task = popTask() { + await task.wait() + } + } + + // MARK: Private + + private func addTask(_ task: ITask) { + lock.lock() + defer { lock.unlock() } + tasks.append(task) + } + + private func popTask() -> ITask? { + lock.lock() + defer { lock.unlock() } + return tasks.popLast() + } +} + +// MARK: ITaskFactory + +extension TestTaskFactory: ITaskFactory { + public func task( + priority: TaskPriority?, + @_inheritActorContext operation: sending @escaping @isolated(any) () async throws -> Success + ) -> Task { + let task = Task(priority: priority, operation: operation) + addTask(task) + return task + } + + public func task( + priority: TaskPriority?, + @_inheritActorContext operation: sending @escaping @isolated(any) () async -> Success + ) -> Task { + let task = Task(priority: priority, operation: operation) + addTask(task) + return task + } + + public func detached( + priority: TaskPriority?, + @_inheritActorContext operation: sending @escaping @isolated(any) () async throws -> Success + ) -> Task { + let task = Task.detached(priority: priority, operation: operation) + addTask(task) + return task + } + + public func detached( + priority: TaskPriority?, + @_inheritActorContext operation: sending @escaping @isolated(any) () async -> Success + ) -> Task { + let task = Task.detached(priority: priority, operation: operation) + addTask(task) + return task + } +}