Swift Academy · レッスン

プロデューサー/コンシューマー、パイプライン、タイムアウト

シンプルな プロデューサー/コンシューマー を構築し、AsyncSequenceで パイプライン を組み合わせます。また、sleepタスクとの競争によって タイムアウト を実装します。

レッスン 1/38 ステップ

「プロデューサー/コンシューマー、パイプライン、タイムアウト」はCoddyKit上の無料Swift Academyレッスンです。 これはレッスン1/3です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSwift Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Swift Academyコースには全3レッスンが含まれています。

このレッスンで作るもの

このレッスンでは、次の3つのパターンを扱います:

  • プロデューサー/コンシューマーとAsyncStream
  • AsyncSequenceの変換を使ったパイプライン
  • タスクを協調的に競合させるタイムアウト

基本的なプロデューサー/コンシューマー

AsyncStreamは、プッシュ型のプロデューサーと、プル型のfor awaitによる消費を橋渡しします。

// Producer pushes values; consumer iterates them asynchronously.
func makeNumbers(count: Int) -> AsyncStream<Int> {
    AsyncStream { continuation in
        Task {
            for i in 1...count {
                continuation.yield(i)
                try? await Task.sleep(nanoseconds: 40_000_000) // ~40ms
            }
            continuation.finish()
        }
    }
}

Task {
    let stream = makeNumbers(count: 5)
    for await n in stream {
        print("consume:", n)
    }
    print("done") // after finish()
}

バックプレッシャーのポリシー

バックプレッシャーを管理するにはbufferingPolicyを選択します(古い要素を破棄、新しい要素を破棄、または小さなストリーム向けに無制限)。

// Use a limited buffering policy to avoid memory spikes.
func makeBufferedNumbers(limit: Int) -> AsyncStream<Int> {
    AsyncStream(bufferingPolicy: .bufferingOldest(limit)) { cont in
        Task {
            for i in 1...20 {
                cont.yield(i)
                // Producer is fast; buffer prevents unbounded growth
            }
            cont.finish()
        }
    }
}

Task {
    let s = makeBufferedNumbers(limit: 5)
    for await n in s {
        // Simulate a slow consumer
        try? await Task.sleep(nanoseconds: 60_000_000)
        print("got:", n)
    }
}

AsyncSequenceを使ったパイプライン

AsyncSequenceは、おなじみの演算子(map、filter、reduce)をサポートしているため、ストリーミングパイプラインを明確に構築できます。

// Compose a pipeline: map -> filter -> reduce over an AsyncSequence.
func numbers(_ n: Int) -> AsyncStream<Int> {
    AsyncStream { cont in
        Task {
            for i in 1...n { cont.yield(i) }
            cont.finish()
        }
    }
}

Task {
    let evensSquaredSum = await numbers(10)
        .map { $0 * $0 }
        .filter { $0.isMultiple(of: 2) }
        .reduce(0, +)
    print("sum:", evensSquaredSum) // 220 (4+16+36+64+100)
}

並列ステージ

CPUバウンドの変換処理にはパイプライン内でタスクグループを使い、その後、好みの順序で結果をまとめ直してください。

// Fan-out/fan-in: process items concurrently, then merge.
// Here we use a task group to transform elements in parallel.
func parallelUppercased(_ input: [String]) async -> [String] {
    await withTaskGroup(of: String.self) { group in
        for s in input {
            group.addTask {
                try? await Task.sleep(nanoseconds: 30_000_000)
                return s.uppercased()
            }
        }
        return await group.reduce(into: [String]()) { $0.append($1) }
    }
}

Task {
    let out = await parallelUppercased(["a","bb","ccc"])
    print(out) // ["A","BB","CCC"]
}

タイムアウトヘルパー

タイムアウトは、操作をsleepタスクと競合させて実装します。処理を節約するため、負けた方をキャンセルしてください。

enum TimeoutError: Error { case timedOut }

func withTimeout<T>(
    seconds: Double,
    operation: @escaping () async throws -> T
) async throws -> T {
    try await withThrowingTaskGroup(of: T.self) { group in
        // Child 1: actual work
        group.addTask { try await operation() }
        // Child 2: the timer
        group.addTask {
            try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
            throw TimeoutError.timedOut
        }
        // First child to finish wins; cancel the rest
        let result = try await group.next()!
        group.cancelAll()
        return result
    }
}

// Demo
func slowFetch() async throws -> String {
    try await Task.sleep(nanoseconds: 300_000_000) // 300ms
    return "OK"
}

Task {
    do {
        let v = try await withTimeout(seconds: 0.1) { try await slowFetch() }
        print("value:", v)
    } catch {
        print("timeout:", error) // expected
    }
}

タイムアウトパターン(競合)

確認問題:タイムアウトを追加するには、どのような方法が適していますか?

まとめ

まとめ:

  • プロデューサー/コンシューマーにはAsyncStreamを使います。
  • map/filter/reduceでAsyncSequenceのパイプラインを組み合わせます。
  • タスクを競合させ、負けた方をキャンセルしてタイムアウトを実装します。
無料で開始

AI チューターと学ぶ Swift — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
122
レッスン
409

よくある質問

「プロデューサー/コンシューマー、パイプライン、タイムアウト」レッスンは無料ですか?

はい。「プロデューサー/コンシューマー、パイプライン、タイムアウト」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Swift Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Swift Academyコースには全3レッスンが含まれています。

「プロデューサー/コンシューマー、パイプライン、タイムアウト」で何を学びますか?

シンプルな プロデューサー/コンシューマー を構築し、AsyncSequenceで パイプライン を組み合わせます。また、sleepタスクとの競争によって タイムアウト を実装します。 ブラウザで直接実行するハンズオンコードでSwift Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Swift Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのSwift Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/3です。

「プロデューサー/コンシューマー、パイプライン、タイムアウト」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このSwift Academyレッスンでコードを書いて実行できますか?

はい。すべてのSwift Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. プロデューサー/コンシューマー、パイプライン、タイムアウト
  2. 従来のコールバック/Combineとasync/awaitの橋渡し
  3. 非同期コードのテスト(XCTest)
← Swift Academyに戻る