concurrency

Swift concurrency patterns with async/await, TaskGroup, and Actor examples

SWIFT swift5.9 ✅ Build Success
swift concurrency async-await taskgroup actor error-handling

concurrency

Swift concurrency patterns with async/await, TaskGroup, and Actor examples

Details

  • Language: swift
  • Environment: swift5.9
  • Tags: swift, concurrency, async-await, taskgroup, actor, error-handling

Build Information

  • Build Success: ✅ Yes
  • Last Built: 2025-08-10T07:39:21.606109-07:00
  • Total Duration: 17.140432417s

Build Output

Building for debugging...
[0/6] Write sources
[0/6] Write Concurrency-entitlement.plist
[2/6] Write swift-version--58304C5D6DBC2206.txt
[4/8] Compiling Concurrency Concurrency.swift
[5/8] Emitting module Concurrency
[5/8] Write Objects.LinkFileList
[6/8] Linking Concurrency
[7/8] Applying Concurrency
Build complete! (4.19s)

Execution Output

[0/1] Planning build
Building for debugging...
[0/3] Write swift-version--58304C5D6DBC2206.txt
Build of product 'Concurrency' complete! (0.19s)
=== Task Group Basic Demo ===
Received metrics: InterfaceMetrics(name: eth0, bytesSent: 1024, timestamp: 2025-08-10 14:39:27 +0000)
Received metrics: InterfaceMetrics(name: wlan0, bytesSent: 1024, timestamp: 2025-08-10 14:39:27 +0000)

=== Context Cancellation Demo ===
Result: Stats from http://api.example.com/stats

=== Error Handling Demo ===
Validation error: Error Domain=ValidationError Code=100 "Invalid interface" UserInfo={NSLocalizedDescription=Invalid interface}

=== Real-World Monitoring Demo ===
Monitor received: InterfaceMetrics(name: eth0, bytesSent: 1024, timestamp: 2025-08-10 14:39:28 +0000)
Monitor received: InterfaceMetrics(name: wlan0, bytesSent: 1024, timestamp: 2025-08-10 14:39:28 +0000)
Monitor received: InterfaceMetrics(name: wlan0, bytesSent: 1024, timestamp: 2025-08-10 14:39:28 +0000)
Monitor received: InterfaceMetrics(name: eth0, bytesSent: 1024, timestamp: 2025-08-10 14:39:28 +0000)
Monitor received: InterfaceMetrics(name: wlan0, bytesSent: 1024, timestamp: 2025-08-10 14:39:28 +0000)
Monitor received: InterfaceMetrics(name: eth0, bytesSent: 1024, timestamp: 2025-08-10 14:39:28 +0000)
Monitor received: InterfaceMetrics(name: wlan0, bytesSent: 1024, timestamp: 2025-08-10 14:39:29 +0000)
Monitor received: InterfaceMetrics(name: eth0, bytesSent: 1024, timestamp: 2025-08-10 14:39:29 +0000)

Testing Output

[0/1] Planning build
Building for debugging...
[0/8] Write sources
[1/8] /Users/jeff/sync_studio/shared/websites/jeff_new/code/concurrency/swift/.build/arm64-apple-macosx/debug/ConcurrencyPackageTests.derived/runner.swift
[2/8] Write sources
[3/8] Write swift-version--58304C5D6DBC2206.txt
[5/8] Compiling ConcurrencyTests ConcurrencyTests.swift
[6/8] Emitting module ConcurrencyTests
[7/10] Emitting module ConcurrencyPackageTests
[8/10] Compiling ConcurrencyPackageTests runner.swift
[8/10] Write Objects.LinkFileList
[9/10] Linking ConcurrencyPackageTests
Build complete! (6.44s)
error: Exited with unexpected signal code 6
Test Suite 'All tests' started at 2025-08-10 07:39:36.556.
Test Suite 'ConcurrencyPackageTests.xctest' started at 2025-08-10 07:39:36.557.
Test Suite 'ConcurrencyPatternsTests' started at 2025-08-10 07:39:36.557.
Test Case '-[ConcurrencyTests.ConcurrencyPatternsTests testFetchNetworkStats_Failure]' started.
Test Case '-[ConcurrencyTests.ConcurrencyPatternsTests testFetchNetworkStats_Failure]' passed (1.029 seconds).
Test Case '-[ConcurrencyTests.ConcurrencyPatternsTests testFetchNetworkStats_Success]' started.
Test Case '-[ConcurrencyTests.ConcurrencyPatternsTests testFetchNetworkStats_Success]' passed (1.009 seconds).
Test Case '-[ConcurrencyTests.ConcurrencyPatternsTests testInterfaceMonitor]' started.
â—‡ Test run started.
↳ Testing Library Version: 124.4
↳ Target Platform: arm64e-apple-macos14.0
✔ Test run with 0 tests passed after 0.001 seconds.

Source Code

Makefile

build:
	swift build

run:
	swift run

test:
	swift test

clean:
	swift package clean

Package.swift

// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "Concurrency",
    platforms: [
        .macOS(.v13)
    ],
    targets: [
        .executableTarget(
            name: "Concurrency",
            dependencies: []
        ),
        .testTarget(
            name: "ConcurrencyTests",
            dependencies: ["Concurrency"]
        ),
    ]
)

Sources/Concurrency/Concurrency.swift

// Sources/ConcurrencyPatterns/ConcurrencyPatterns.swift

import Foundation

// Mock types for demonstration purposes
struct InterfaceMetrics: CustomStringConvertible {
    let name: String
    let bytesSent: Int
    let timestamp: Date

    var description: String {
        return "InterfaceMetrics(name: \(name), bytesSent: \(bytesSent), timestamp: \(timestamp))"
    }
}

struct NetworkInterface {
    let name: String
}

// --- BEGIN BLOG EXAMPLE: TASK GROUP BASIC ---
// Line 25-45
func monitorInterfacesBasic() async throws {
    let interfaces = [
        NetworkInterface(name: "eth0"),
        NetworkInterface(name: "wlan0"),
    ]

    try await withThrowingTaskGroup(of: InterfaceMetrics.self) { group in
        for interface in interfaces {
            group.addTask {
                // Simulate work
                try await Task.sleep(for: .milliseconds(100))
                return InterfaceMetrics(
                    name: interface.name,
                    bytesSent: 1024,
                    timestamp: Date()
                )
            }
        }

        for try await metrics in group {
            print("Received metrics: \(metrics)")
        }
    }
}
// --- END BLOG EXAMPLE ---

// --- BEGIN BLOG EXAMPLE: ASYNC/AWAIT WITH ERROR HANDLING ---
// Line 65-90
func fetchNetworkStats(from endpoint: String) async throws -> String {
    // Simulate network request with potential delay
    try await Task.sleep(for: .seconds(1))

    guard !endpoint.contains("error") else {
        throw NSError(
            domain: "NetworkError", code: 404, userInfo: [NSLocalizedDescriptionKey: "Not Found"])
    }

    return "Stats from \(endpoint)"
}

func contextCancellationDemo() async {
    do {
        let result = try await fetchNetworkStats(from: "http://api.example.com/stats")
        print("Result: \(result)")
    } catch {
        print("Request failed: \(error)")
    }
}
// --- END BLOG EXAMPLE ---

// --- BEGIN BLOG EXAMPLE: ERROR HANDLING WITH TASK GROUP ---
// Line 105-135
func validateInterfaces(_ interfaces: [String]) async throws {
    try await withThrowingTaskGroup(of: Void.self) { group in
        for interface in interfaces {
            group.addTask {
                // Simulate validation
                try await Task.sleep(for: .milliseconds(50))
                if interface == "bad0" {
                    throw NSError(
                        domain: "ValidationError", code: 100,
                        userInfo: [NSLocalizedDescriptionKey: "Invalid interface"])
                }
            }
        }

        // Fail fast on first error
        try await group.waitForAll()
    }
}

func errorHandlingDemo() async {
    let interfaces = ["eth0", "wlan0", "bad0", "lo"]

    do {
        try await validateInterfaces(interfaces)
        print("All interfaces validated successfully")
    } catch {
        print("Validation error: \(error)")
    }
}
// --- END BLOG EXAMPLE ---

// --- BEGIN BLOG EXAMPLE: REAL-WORLD MONITORING STRUCTURE ---
// Line 150-200
actor InterfaceMonitor {
    private var monitoringTask: Task<Void, Never>?
    private let metricsHandler: (InterfaceMetrics) async -> Void

    init(metricsHandler: @escaping (InterfaceMetrics) async -> Void) {
        self.metricsHandler = metricsHandler
    }

    func start() {
        monitoringTask = Task { [weak self] in
            await withTaskGroup(of: Void.self) { group in
                let interfaces = [
                    NetworkInterface(name: "eth0"),
                    NetworkInterface(name: "wlan0"),
                ]

                for interface in interfaces {
                    group.addTask { [interface, weak self] in
                        await self?.monitorInterface(interface.name)
                    }
                }
            }
        }
    }

    private func monitorInterface(_ name: String) async {
        while !Task.isCancelled {
            do {
                // Simulate metric collection
                try await Task.sleep(for: .milliseconds(100))

                // Check cancellation after sleep
                if Task.isCancelled { break }

                let metrics = InterfaceMetrics(
                    name: name,
                    bytesSent: 1024,
                    timestamp: Date()
                )
                await metricsHandler(metrics)
            } catch is CancellationError {
                break
            } catch {
                print("Error collecting metrics for \(name): \(error)")
                try? await Task.sleep(for: .milliseconds(100))
            }
        }
    }

    func stop() async {
        monitoringTask?.cancel()
        monitoringTask = nil
    }
}

func realWorldDemo() async {
    var receivedMetrics: [InterfaceMetrics] = []

    let monitor = InterfaceMonitor { metrics in
        receivedMetrics.append(metrics)
        print("Monitor received: \(metrics)")
    }

    await monitor.start()

    // Let it run for a bit
    try? await Task.sleep(for: .milliseconds(500))
    await monitor.stop()

    // Give tasks time to respond to cancellation before function exits
    try? await Task.sleep(for: .milliseconds(100))
}
// --- END BLOG EXAMPLE ---

// MARK: - Main Execution
@main
struct ConcurrencyPatternsDemo {
    static func main() async {
        print("=== Task Group Basic Demo ===")
        try? await monitorInterfacesBasic()

        print("\n=== Context Cancellation Demo ===")
        await contextCancellationDemo()

        print("\n=== Error Handling Demo ===")
        await errorHandlingDemo()

        print("\n=== Real-World Monitoring Demo ===")
        await realWorldDemo()
    }
}

Tests/ConcurrencyTests/ConcurrencyTests.swift

// Tests/ConcurrencyPatternsTests/ConcurrencyPatternsTests.swift

import XCTest

@testable import Concurrency

final class ConcurrencyPatternsTests: XCTestCase {

    // --- BEGIN BLOG EXAMPLE: TASK GROUP TEST ---
    // Line 15-35
    func testMonitorInterfacesBasic() async throws {
        let expectation = XCTestExpectation(description: "Should complete monitoring")

        Task {
            try? await monitorInterfacesBasic()
            expectation.fulfill()
        }

        await fulfillment(of: [expectation], timeout: 1.0)
    }
    // --- END BLOG EXAMPLE ---

    // --- BEGIN BLOG EXAMPLE: ASYNC/AWAIT TEST ---
    // Line 50-70
    func testFetchNetworkStats_Success() async throws {
        let result = try await fetchNetworkStats(from: "http://api.example.com/stats")
        XCTAssertTrue(result.contains("Stats from"))
    }

    func testFetchNetworkStats_Failure() async {
        do {
            _ = try await fetchNetworkStats(from: "http://api.example.com/error")
            XCTFail("Expected error but got success")
        } catch {
            // Success - we expected an error
        }
    }
    // --- END BLOG EXAMPLE ---

    // --- BEGIN BLOG EXAMPLE: ERROR HANDLING TEST ---
    // Line 85-105
    func testValidateInterfaces_Success() async throws {
        let interfaces = ["eth0", "wlan0", "lo"]
        try await validateInterfaces(interfaces)
        // If we get here without error, test passes
    }

    func testValidateInterfaces_Failure() async {
        let interfaces = ["eth0", "wlan0", "bad0", "lo"]

        do {
            try await validateInterfaces(interfaces)
            XCTFail("Expected validation error")
        } catch {
            // Success - we expected an error
        }
    }
    // --- END BLOG EXAMPLE ---

    // --- BEGIN BLOG EXAMPLE: REAL-WORLD MONITOR TEST ---
    // Line 120-145
    func testInterfaceMonitor() async throws {
        var receivedMetrics: [InterfaceMetrics] = []
        let expectation = XCTestExpectation(description: "Should receive metrics")

        let monitor = InterfaceMonitor { metrics in
            receivedMetrics.append(metrics)
            if receivedMetrics.count >= 2 {
                expectation.fulfill()
            }
        }

        await monitor.start()

        // Wait for metrics to be collected
        await fulfillment(of: [expectation], timeout: 1.0)

        // Clean up
        await monitor.stop()

        // Verify we received metrics
        XCTAssertTrue(
            receivedMetrics.count >= 2, "Expected at least 2 metrics, got \(receivedMetrics.count)")
    }
    // --- END BLOG EXAMPLE ---
}

autodoc.yaml

title: concurrency
description: Swift concurrency patterns with async/await, TaskGroup, and Actor examples
tags:
  - swift
  - concurrency
  - async-await
  - taskgroup
  - actor
  - error-handling
environment: swift5.9
created: 2025-08-09T17:30:40.609919-07:00
author: Jeff Shumate