{"id":13462734,"url":"https://github.com/dfed/swift-async-queue","last_synced_at":"2025-04-04T13:10:11.213Z","repository":{"id":63647806,"uuid":"569520920","full_name":"dfed/swift-async-queue","owner":"dfed","description":"A library of queues that enable sending ordered tasks to asynchronous contexts","archived":false,"fork":false,"pushed_at":"2025-02-23T19:15:16.000Z","size":120,"stargazers_count":196,"open_issues_count":1,"forks_count":11,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-28T12:06:43.132Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dfed.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"Contributing.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"dfed","buy_me_a_coffee":"dfed","custom":"https://cash.app/$dan"}},"created_at":"2022-11-23T02:28:24.000Z","updated_at":"2025-03-26T18:20:45.000Z","dependencies_parsed_at":"2023-11-29T00:29:53.678Z","dependency_job_id":"6dc70825-c7da-46f5-8366-d12f66cbf43d","html_url":"https://github.com/dfed/swift-async-queue","commit_stats":{"total_commits":73,"total_committers":3,"mean_commits":"24.333333333333332","dds":"0.15068493150684936","last_synced_commit":"be7300e293ccb6a318f36f5d64675dc891ab1b78"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfed%2Fswift-async-queue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfed%2Fswift-async-queue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfed%2Fswift-async-queue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfed%2Fswift-async-queue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dfed","download_url":"https://codeload.github.com/dfed/swift-async-queue/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247182348,"owners_count":20897379,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T13:00:19.862Z","updated_at":"2025-04-04T13:10:11.188Z","avatar_url":"https://github.com/dfed.png","language":"Swift","funding_links":["https://github.com/sponsors/dfed","https://buymeacoffee.com/dfed","https://cash.app/$dan"],"categories":["Libs","Recently Updated","Architecture and State","Concurrency","concurrency"],"sub_categories":["Concurrency","[Who Wants to Be a Millionare](https://www.boardgamecapital.com/who-wants-to-be-a-millionaire-rules.htm)","Linter"],"readme":"# swift-async-queue\n[![CI Status](https://img.shields.io/github/actions/workflow/status/dfed/swift-async-queue/ci.yml?branch=main)](https://github.com/dfed/swift-async-queue/actions?query=workflow%3ACI+branch%3Amain)\n[![codecov](https://codecov.io/gh/dfed/swift-async-queue/branch/main/graph/badge.svg?token=nZBHcZZ63F)](https://codecov.io/gh/dfed/swift-async-queue)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://spdx.org/licenses/MIT.html)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fdfed%2Fswift-async-queue%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/dfed/swift-async-queue)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fdfed%2Fswift-async-queue%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/dfed/swift-async-queue)\n\nA library of queues that enable sending ordered tasks from nonisolated to asynchronous contexts.\n\n## Task Ordering and Swift Concurrency\n\nTasks sent from a nonisolated context to an asynchronous context in Swift Concurrency are inherently unordered. Consider the following test:\n\n```swift\nfunc testActorTaskOrdering() async {\n    actor Counter {\n        func incrementAndAssertCountEquals(_ expectedCount: Int) {\n            count += 1\n            let incrementedCount = count\n            XCTAssertEqual(incrementedCount, expectedCount) // often fails\n        }\n\n        private var count = 0\n    }\n\n    let counter = Counter()\n    var tasks = [Task\u003cVoid, Never\u003e]()\n    for iteration in 1...100 {\n        tasks.append(Task {\n            await counter.incrementAndAssertCountEquals(iteration)\n        })\n    }\n    // Wait for all enqueued tasks to finish.\n    for task in tasks {\n        _ = await task.value\n    }\n}\n```\n\nBecause the `Task` is spawned from a nonisolated execution context, the ordering of the scheduled asynchronous work is not guaranteed.\n\nWhile [actors](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#ID645) are great at serializing tasks, there is no simple way in the standard Swift library to send ordered tasks to them from a nonisolated synchronous context, or from multiple execution contexts.\n\n### Executing asynchronous tasks in FIFO order\n\nUse a `FIFOQueue` to execute asynchronous tasks enqueued from a nonisolated context in FIFO order. Tasks sent to one of these queues are guaranteed to begin _and end_ executing in the order in which they are enqueued. A `FIFOQueue` executes tasks in a similar manner to a `DispatchQueue`: enqueued tasks executes atomically, and the program will deadlock if a task executing on a `FIFOQueue` awaits results from the queue on which it is executing.\n\nA `FIFOQueue` can easily execute asynchronous tasks from a nonisolated context in FIFO order:\n```swift\nfunc testFIFOQueueOrdering() async {\n    actor Counter {\n        nonisolated\n        func incrementAndAssertCountEquals(_ expectedCount: Int) {\n            queue.enqueue {\n                await self.increment()\n                let incrementedCount = await self.count\n                XCTAssertEqual(incrementedCount, expectedCount) // always succeeds\n            }\n        }\n\n        func flushQueue() async {\n            await queue.enqueueAndWait { }\n        }\n\n        func increment() {\n            count += 1\n        }\n\n        var count = 0\n\n        private let queue = FIFOQueue()\n    }\n\n    let counter = Counter()\n    for iteration in 1...100 {\n        counter.incrementAndAssertCountEquals(iteration)\n    }\n    // Wait for all enqueued tasks to finish.\n    await counter.flushQueue()\n}\n```\n\nFIFO execution has a key downside: the queue must wait for all previously enqueued work – including suspended work – to complete before new work can begin. If you desire new work to start when a prior task suspends, utilize an `ActorQueue`.\n\n### Sending ordered asynchronous tasks to Actors from a nonisolated context\n\nUse an `ActorQueue` to send ordered asynchronous tasks to an `actor`’s isolated context from nonisolated or synchronous contexts. Tasks sent to an actor queue are guaranteed to begin executing in the order in which they are enqueued. However, unlike a `FIFOQueue`, execution order is guaranteed only until the first [suspension point](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#ID639) within the enqueued task. An `ActorQueue` executes tasks within the its adopted actor’s isolated context, resulting in `ActorQueue` task execution having the same properties as `actor` code execution: code between suspension points is executed atomically, and tasks sent to a single `ActorQueue` can await results from the queue without deadlocking.\n\nAn instance of an `ActorQueue` is designed to be utilized by a single `actor` instance: tasks sent to an `ActorQueue` utilize the isolated context of the queue‘s adopted `actor` to serialize tasks. As such, there are a couple requirements that must be met when dealing with an `ActorQueue`:\n1. The lifecycle of any `ActorQueue` should not exceed the lifecycle of its `actor`. It is strongly recommended that an `ActorQueue` be a `private let` constant on the adopted `actor`. Enqueuing a task to an `ActorQueue` instance after its adopted `actor` has been deallocated will result in a crash.\n2. An `actor` utilizing an `ActorQueue` should set the adopted execution context of the queue to `self` within the `actor`’s `init`. Failing to set an adopted execution context prior to enqueuing work on an `ActorQueue` will result in a crash.\n\nAn `ActorQueue` can easily enqueue tasks that execute on an actor’s isolated context from a nonisolated context in order:\n```swift\nfunc testActorQueueOrdering() async {\n    actor Counter {\n        init() {\n            // Adopting the execution context in `init` satisfies requirement #2 above.\n            queue.adoptExecutionContext(of: self)\n        }\n\n        nonisolated\n        func incrementAndAssertCountEquals(_ expectedCount: Int) {\n            queue.enqueue { myself in\n                myself.count += 1\n                XCTAssertEqual(expectedCount, myself.count) // always succeeds\n            }\n        }\n\n        func flushQueue() async {\n            await queue.enqueueAndWait { _ in }\n        }\n\n        private var count = 0\n        // Making the queue a private let constant satisfies requirement #1 above.\n        private let queue = ActorQueue\u003cCounter\u003e()\n    }\n\n    let counter = Counter()\n    for iteration in 1...100 {\n        counter.incrementAndAssertCountEquals(iteration)\n    }\n    // Wait for all enqueued tasks to finish.\n    await counter.flushQueue()\n}\n```\n\n### Sending ordered asynchronous tasks to the `@MainActor` from a nonisolated context\n\nUse a `MainActorQueue` to send ordered asynchronous tasks to the `@MainActor`’s isolated context from nonisolated or synchronous contexts. Tasks sent to this queue type are guaranteed to begin executing in the order in which they are enqueued. Like an `ActorQueue`, execution order is guaranteed only until the first [suspension point](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#ID639) within the enqueued task. A `MainActorQueue` executes tasks within its adopted actor’s isolated context, resulting in `MainActorQueue` task execution having the same properties as a `@MainActor`'s' code execution: code between suspension points is executed atomically, and tasks sent to a single `MainActorQueue` can await results from the queue without deadlocking.\n\nA `MainActorQueue` can easily execute asynchronous tasks from a nonisolated context in FIFO order:\n```swift\n@MainActor\nfunc testMainActorQueueOrdering() async {\n    @MainActor\n    final class Counter {\n        nonisolated\n        func incrementAndAssertCountEquals(_ expectedCount: Int) {\n            MainActorQueue.shared.enqueue {\n                self.increment()\n                let incrementedCount = self.count\n                XCTAssertEqual(incrementedCount, expectedCount) // always succeeds\n            }\n        }\n\n        func flushQueue() async {\n            await MainActorQueue.shared.enqueueAndWait { }\n        }\n\n        func increment() {\n            count += 1\n        }\n\n        var count = 0\n    }\n\n    let counter = Counter()\n    for iteration in 1...100 {\n        counter.incrementAndAssertCountEquals(iteration)\n    }\n    // Wait for all enqueued tasks to finish.\n    await counter.flushQueue()\n}\n```\n\n## Requirements\n\n* Xcode 16.0 or later.\n* iOS 13 or later.\n* tvOS 13 or later.\n* watchOS 6 or later.\n* macOS 10.15 or later.\n* Swift 5.10 or later.\n\n## Installation\n\n### Swift Package Manager\n\nTo install swift-async-queue in your project with [Swift Package Manager](https://github.com/apple/swift-package-manager), the following lines can be added to your `Package.swift` file:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/dfed/swift-async-queue\", from: \"0.6.0\"),\n]\n```\n\n### CocoaPods\n\nTo install swift-async-queue in your project with [CocoaPods](http://cocoapods.org), add the following to your `Podfile`:\n\n```\npod 'AsyncQueue', '~\u003e 0.6.0'\n```\n\n## Contributing\n\nI’m glad you’re interested in swift-async-queue, and I’d love to see where you take it. Please read the [contributing guidelines](Contributing.md) prior to submitting a Pull Request.\n\nThanks, and happy queueing!\n\n## Developing\n\nDouble-click on `Package.swift` in the root of the repository to open the project in Xcode.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdfed%2Fswift-async-queue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdfed%2Fswift-async-queue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdfed%2Fswift-async-queue/lists"}