Untuk mempertahankan perhitungan Swift yang berhasil saat tugas-tugas saudara gagal, tangkap error setiap perhitungan di dalam tugas anaknya dan kembalikan nilai hasil. Contoh baris perintah Mac ini mengumpulkan setiap hasil, mempertahankan urutan input, dan menjaga nilai yang berhasil tetap tersedia.

Tugas ini adalah perhitungan batch sintetis sederhana: kuadratkan daftar bilangan bulat nonnegatif dan laporkan input mana yang berhasil. Input negatif dan overflow bilangan bulat menghasilkan kegagalan masing-masing. Semua data perhitungan tetap berada di memori; program mencetak hasilnya di Terminal.

Mengapa satu error dapat menghentikan pengumpulan hasil?

Tugas anak yang melempar error tidak langsung membatalkan tugas-tugas saudaranya. Batas yang berdampak adalah isi grup tugas: ketika sebuah error keluar dari isi tersebut, Swift membatalkan anak-anak yang tersisa. Apple menjelaskan perbedaan ini dalam referensinya untuk withThrowingTaskGroup(of:returning:isolation:body:).

Akibatnya, loop iterasi yang melempar error dengan catch di bagian luar dapat berhenti mengumpulkan hasil saat pertama kali menemukan error. Nilai sebelumnya mungkin sudah disimpan, tetapi loop tidak dilanjutkan setelah catch tersebut. Anak-anak yang tersisa menerima pembatalan jika error keluar dari isi grup.

Untuk perhitungan independen, nyatakan kebijakan kegagalannya secara eksplisit. Contoh di bawah menggunakan grup tugas yang tidak melempar error, dengan anak-anak yang mengembalikan keberhasilan, kegagalan, atau pembatalan. Perhitungannya sendiri tetap melempar error; pembungkusnya mengubah error itu menjadi nilai sebelum mencapai grup.

Ini sesuai dengan batas yang didokumentasikan Swift: anak-anak dalam grup tugas yang tidak melempar error tidak dapat meneruskan error yang dilempar, jadi mereka harus menanganinya secara internal atau merepresentasikan hasilnya sebagai nilai kembalian. Lihat dokumentasi TaskGroup dari Apple.

Tentukan nilai yang harus dikembalikan setiap input

Kontrak batch-nya adalah satu hasil untuk setiap posisi input. Perhitungan yang berhasil mengembalikan kuadrat bilangan bulat yang tepat. Perhitungan yang ditolak mengembalikan deskripsi kegagalan. Perhitungan yang dilewati karena pembatalan terdeteksi mengembalikan hasil pembatalan.

Situasi Hasil Perilaku batch
Input nonnegatif yang kuadratnya muat dalam Int64 Berhasil berisi nilai kuadrat Pertahankan nilainya
Input negatif Gagal: negativeInput Lanjutkan mengumpulkan hasil lainnya
Kuadrat melebihi rentang Int64 Gagal: overflow Lanjutkan mengumpulkan hasil lainnya
Pembatalan terdeteksi sebelum perhitungan Dibatalkan Pertahankan hasil yang sudah selesai dan lewati pekerjaan yang belum dimulai

Menolak input negatif adalah aturan aplikasi ilustratif; bilangan negatif dapat dikuadratkan secara matematis. Memisahkan aturan itu dari overflow membuat contoh ini menunjukkan dua kondisi kegagalan yang berbeda.

Untuk perkalian, gunakan multipliedReportingOverflow(by:) dan periksa tanda overflow-nya. Apple menetapkan bahwa nilai parsial yang dikembalikan adalah hasil kali lengkap hanya jika tanda tersebut bernilai false; jika tidak, nilainya terpotong. Contoh ini membuang nilai terpotong tersebut dan melempar error. Lihat Int64.multipliedReportingOverflow(by:).

Setiap hasil juga membawa indeks inputnya. Swift tidak menjamin urutan penyelesaian tugas anak, jadi hanya menambahkan nilai yang selesai tidak dapat mempertahankan urutan semula. Panduan bahasa menjelaskan perilaku penjadwalan ini dalam Concurrency.

Jalankan contoh lengkap di Mac Anda

Gunakan Mac dengan toolchain Swift 6 atau yang lebih baru. Di Terminal, jalankan swift --version untuk memeriksa compiler yang terpasang, sesuai rekomendasi Panduan Memulai Swift.org. Jika Swift belum tersedia, ikuti tautan instalasi dalam panduan tersebut sebelum melanjutkan.

Simpan program lengkap berikut sebagai Calculations.swift di folder kerja. Program ini mengimpor Foundation untuk memilih batas konkurensi awal dari jumlah prosesor aktif.

import Foundation

enum CalculationError: Error {
    case negativeInput
    case overflow
}

enum Outcome: Sendable {
    case success(Int64)
    case failure(String)
    case cancelled
}

struct Row: Sendable {
    let index: Int
    let outcome: Outcome
}

func square(_ input: Int64) throws -> Int64 {
    guard input >= 0 else {
        throw CalculationError.negativeInput
    }
    let product = input.multipliedReportingOverflow(by: input)
    guard !product.overflow else {
        throw CalculationError.overflow
    }
    return product.partialValue
}

func evaluate(index: Int, input: Int64) -> Row {
    do {
        try Task.checkCancellation()
        let value = try square(input)
        return Row(index: index, outcome: .success(value))
    } catch is CancellationError {
        return Row(index: index, outcome: .cancelled)
    } catch {
        return Row(
            index: index,
            outcome: .failure(String(describing: error))
        )
    }
}

func collect(
    _ inputs: [Int64],
    maxConcurrent: Int
) async -> [Row] {
    guard !inputs.isEmpty else { return [] }
    let width = min(inputs.count, max(1, maxConcurrent))

    return await withTaskGroup(of: Row.self) { group in
        var rows: [Row] = []
        rows.reserveCapacity(inputs.count)

        for index in 0..<width {
            let input = inputs[index]
            group.addTask {
                evaluate(index: index, input: input)
            }
        }
        var nextIndex = width

        while let row = await group.next() {
            rows.append(row)

            if Task.isCancelled {
                while nextIndex < inputs.count {
                    rows.append(Row(
                        index: nextIndex,
                        outcome: .cancelled
                    ))
                    nextIndex += 1
                }
            } else if nextIndex < inputs.count {
                let index = nextIndex
                let input = inputs[index]
                nextIndex += 1
                group.addTask {
                    evaluate(index: index, input: input)
                }
            }
        }

        return rows.sorted { $0.index < $1.index }
    }
}

@main
struct CalculationDemo {
    static func main() async {
        let inputs: [Int64] = [3, -2, 4, Int64.max, 0]
        let width = min(
            4,
            max(1, ProcessInfo.processInfo.activeProcessorCount)
        )
        let rows = await collect(inputs, maxConcurrent: width)
        var successes: [Int64] = []

        for row in rows {
            let input = inputs[row.index]
            switch row.outcome {
            case .success(let value):
                successes.append(value)
                print("[\(row.index)] \(input): success \(value)")
            case .failure(let message):
                print("[\(row.index)] \(input): failure \(message)")
            case .cancelled:
                print("[\(row.index)] \(input): cancelled")
            }
        }
        print("Successful values: \(successes)")
    }
}

Dari folder tersebut, kompilasi dan jalankan program:

swiftc -swift-version 6 -parse-as-library Calculations.swift -o Calculations
./Calculations

Opsi -parse-as-library memungkinkan file sumber tunggal ini menggunakan titik masuk @main yang eksplisit. Eksekutabelnya melakukan perhitungan tanpa membaca atau menulis data aplikasi. Swift tidak tersedia di lingkungan riset, jadi program ini telah diperiksa tetapi tidak dikompilasi atau dijalankan di sana.

Untuk input yang diberikan tanpa pembatalan, keluaran yang diharapkan adalah:

[0] 3: success 9
[1] -2: failure negativeInput
[2] 4: success 16
[3] 9223372036854775807: failure overflow
[4] 0: success 0
Successful values: [9, 16, 0]

Urutan yang dicetak berasal dari pengurutan berdasarkan indeks input setelah pengumpulan. Urutan eksekusi dapat bervariasi tanpa mengubah penyajian ini. Array nilai yang berhasil dibuat sesudahnya, sehingga seluruh laporan hasil tetap tersedia untuk diperiksa.

Bagaimana pengumpul mempertahankan setiap hasil yang berhasil?

Tugas induk memiliki array hasil. Setiap anak menangkap satu indeks dan satu input yang tidak berubah, menghitung hasilnya, lalu mengembalikan satu baris. Anak-anak tidak pernah menambahkan langsung ke array milik induk. Ini mengikuti pendekatan dalam panduan bahasa, yaitu mengumpulkan hasil anak di dalam isi grup tugas. Lihat Concurrency.

Pengumpul awalnya mengirim sejumlah anak yang dibatasi. Setiap baris yang selesai membebaskan satu slot untuk input lain. Karena itu, perhitungan yang gagal memajukan batch dengan cara yang sama seperti perhitungan yang berhasil: keduanya mengembalikan baris biasa, dan pengumpulan berlanjut.

Foundation activeProcessorCount melaporkan jumlah inti pemrosesan aktif yang tersedia di komputer. Contoh ini membatasi jumlah tersebut hingga empat sebagai pilihan penjadwalan ilustratif. Batas itu bukan nilai optimal yang terukur ataupun jaminan bahwa empat perhitungan berjalan secara bersamaan. Lihat dokumentasi activeProcessorCount dari Apple.

Batas konkurensi membatasi jumlah tugas anak yang masih berjalan, sedangkan array hasil tetap bertambah seiring jumlah input. Ini cocok untuk batch desktop kecil. Untuk operasi kuadrat yang sangat ringan ini, contoh menunjukkan penanganan hasil; contoh ini tidak mengklaim kinerja lebih baik daripada loop berurutan.

Penjaga input kosong mengembalikan array kosong. Batas konkurensi nol atau negatif dijepit ke satu. Nilai input duplikat tetap terpisah karena identitas ditentukan oleh posisi, sehingga dua input yang sama tetap menghasilkan dua baris.

Apa yang terjadi saat tugas induk dibatalkan?

Pembatalan Swift bersifat kooperatif. Membatalkan tugas induk meneruskan pembatalan ke anak-anak terstruktur, tetapi anak harus mendeteksi sinyal itu dan meresponsnya. Dokumentasi TaskGroup dari Apple menjelaskan penerusan ini sekaligus perlunya penanganan kooperatif.

Di sini, evaluate memanggil Task.checkCancellation() sebelum menghitung. Apple mendokumentasikan bahwa metode ini melempar CancellationError saat pembatalan terdeteksi; pembungkus menangkapnya secara terpisah dan mengembalikan baris yang dibatalkan. Lihat checkCancellation().

Setelah pengumpul mendeteksi pembatalan, ia menandai input yang belum dikirim sebagai dibatalkan. Anak-anak yang sudah dikirim tetap ditunggu hingga selesai. Hasil mereka yang berhasil tetap ada dalam laporan, dan anak-anak yang mendeteksi pembatalan mengembalikan baris yang dibatalkan.

Pembatalan dapat terjadi setelah pemeriksaan anak. Anak itu mungkin tetap selesai dengan sukses, dan kontrak ini menerimanya. Jika perhitungan menjadi loop panjang, tambahkan pemeriksaan pembatalan pada interval yang sesuai di dalam perhitungan tersebut. Grup tetap menunggu semua anak yang telah dikirim hingga selesai sebelum kembali, sebagaimana didokumentasikan untuk TaskGroup.

Kapan sebaiknya Anda menggunakan grup tugas yang melempar error?

Jika anak-anak yang sudah ada melempar error dan Anda ingin mempertahankan antarmuka itu, grup tugas yang melempar error juga mendukung pengumpulan hasil individual. Panggil nextResult() berulang kali dan gunakan switch pada setiap keberhasilan atau kegagalan yang dikembalikan sampai hasil opsionalnya kosong. Tidak seperti next(), metode ini mengembalikan nilai Result alih-alih melempar error anak kepada pemanggil. Apple mendokumentasikan perilaku urutan penyelesaiannya dalam nextResult().

Untuk desain itu, sertakan identitas input dalam nilai yang berhasil dan dalam error apa pun yang perlu dikaitkan dengan input. Error aritmetika biasa tidak menunjukkan input mana dari beberapa input yang menyebabkannya. Pilih juga kebijakan pembatalan yang eksplisit alih-alih memperlakukan pembatalan sebagai kegagalan perhitungan biasa.

Gunakan pendekatan yang mengembalikan hasil saat persyaratan utamanya adalah laporan lengkap untuk setiap input. Gunakan grup yang melempar error saat meneruskan error keluar dari grup adalah perilaku batch yang diinginkan, atau saat menggunakan operasi yang sudah ada dan melempar error melalui nextResult() terasa lebih praktis.

Untuk tugas perhitungan lokal ini, kontrak intinya sederhana: pertahankan setiap nilai yang berhasil, catat setiap input yang gagal, dan lanjutkan pengumpulan. Membawa indeks input dalam setiap hasil membuat laporan akhir tetap berguna meski perhitungan selesai dalam urutan yang tidak dapat diprediksi.

Sumber