Untuk mengekalkan pengiraan Swift yang berjaya apabila tugas seangkatan gagal, tangkap ralat setiap pengiraan dalam tugas anaknya dan pulangkan nilai hasil. Contoh baris perintah Mac ini mengumpulkan setiap hasil, mengekalkan susunan input dan memastikan nilai yang berjaya tersedia.
Tugas ini ialah pengiraan kelompok sintetik yang kecil: kuasakan dua senarai integer bukan negatif dan laporkan input yang berjaya. Input negatif dan limpahan integer menghasilkan kegagalan masing-masing. Semua data pengiraan kekal dalam ingatan; program mencetak hasilnya dalam Terminal.
Mengapa satu ralat boleh mengganggu pengumpulan hasil?
Tugas anak yang melontarkan ralat tidak serta-merta membatalkan tugas seangkatan. Batas yang membawa kesan ialah badan kumpulan tugas: apabila ralat terlepas daripada badan itu, Swift membatalkan anak-anak yang masih berjalan. Apple mendokumenkan perbezaan ini dalam rujukannya untuk withThrowingTaskGroup(of:returning:isolation:body:).
Oleh itu, gelung lelaran yang melontarkan ralat dengan tangkapan luar boleh berhenti mengumpulkan hasil sebaik sahaja ralat pertama diperhatikan. Nilai terdahulu mungkin sudah disimpan, tetapi gelung tidak bersambung semula selepas tangkapan itu. Anak-anak yang masih berjalan menerima pembatalan jika ralat terlepas daripada badan kumpulan.
Untuk pengiraan bebas, nyatakan dasar kegagalan dengan jelas. Contoh di bawah menggunakan kumpulan tugas yang tidak melontarkan ralat, dengan anak-anak memulangkan kejayaan, kegagalan atau pembatalan. Pengiraan itu sendiri masih melontarkan ralat; pembungkusnya menukar ralat itu menjadi nilai sebelum sampai ke kumpulan.
Ini selaras dengan batas yang didokumenkan dalam Swift: anak-anak dalam kumpulan tugas yang tidak melontarkan ralat tidak boleh menyebarkan ralat yang dilontarkan, jadi mereka mesti mengendalikannya secara dalaman atau mewakili hasilnya sebagai nilai pulangan. Lihat dokumentasi TaskGroup Apple.
Tentukan perkara yang mesti dipulangkan bagi setiap input
Kontrak kelompok ialah satu hasil bagi setiap kedudukan input. Pengiraan yang berjaya memulangkan kuasa dua integer yang tepat. Pengiraan yang ditolak memulangkan perihalan kegagalan. Pengiraan yang dilangkau kerana pembatalan dikesan memulangkan hasil pembatalan.
| Situasi | Hasil | Tingkah laku kelompok |
|---|---|---|
Input bukan negatif yang kuasa duanya muat dalam Int64 |
Kejayaan yang mengandungi kuasa dua | Kekalkan nilai itu |
| Input negatif | Kegagalan: negativeInput |
Terus kumpulkan hasil lain |
Kuasa dua melebihi julat Int64 |
Kegagalan: overflow |
Terus kumpulkan hasil lain |
| Pembatalan dikesan sebelum pengiraan | Dibatalkan | Kekalkan hasil yang telah selesai dan langkau kerja yang belum bermula |
Penolakan input negatif ialah peraturan aplikasi sebagai contoh; nombor negatif boleh dikuasakan dua secara matematik. Mengasingkan peraturan itu daripada limpahan membolehkan contoh ini menunjukkan dua keadaan kegagalan yang berbeza.
Untuk pendaraban, gunakan multipliedReportingOverflow(by:) dan periksa bendera limpahannya. Apple menetapkan bahawa nilai separa yang dipulangkan ialah hasil darab lengkap hanya apabila bendera itu bernilai palsu; jika tidak, nilainya dipotong. Contoh ini membuang nilai terpotong itu dan melontarkan ralat. Lihat Int64.multipliedReportingOverflow(by:).
Setiap hasil turut membawa indeks inputnya. Swift tidak menjamin susunan tugas anak selesai, jadi menambah nilai yang selesai sahaja tidak dapat mengekalkan susunan asal. Panduan bahasa menerangkan tingkah laku penjadualan ini dalam Keserentakan.
Jalankan contoh lengkap pada Mac anda
Gunakan Mac dengan toolchain Swift 6 atau lebih baharu. Dalam Terminal, jalankan swift --version untuk menyemak pengkompil yang dipasang, seperti yang disarankan dalam Panduan Bermula Swift.org. Jika Swift tidak tersedia, ikuti pautan pemasangan dalam panduan itu sebelum meneruskan.
Simpan program lengkap berikut sebagai Calculations.swift dalam folder kerja. Program ini mengimport Foundation untuk memilih had keserentakan awal berdasarkan bilangan pemproses yang 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 itu, kompil dan jalankan program:
swiftc -swift-version 6 -parse-as-library Calculations.swift -o Calculations
./Calculations
Pilihan -parse-as-library membolehkan fail sumber tunggal ini menggunakan titik masuk @main yang dinyatakan secara eksplisit. Boleh laku itu melakukan pengiraan tanpa membaca atau menulis data aplikasi. Swift tidak tersedia dalam persekitaran penyelidikan, jadi program ini telah diperiksa tetapi tidak dikompil atau dijalankan di sana.
Untuk input yang dibekalkan tanpa pembatalan, output yang dijangka ialah:
[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]
Susunan yang dicetak datang daripada pengisihan mengikut indeks input selepas pengumpulan. Susunan pelaksanaan boleh berubah tanpa mengubah persembahan ini. Tatasusunan nilai berjaya dibina selepas itu, manakala laporan hasil penuh kekal tersedia untuk diperiksa.
Bagaimanakah pengumpul mengekalkan setiap hasil yang berjaya?
Tugas induk memiliki tatasusunan hasil. Setiap anak menangkap satu indeks dan satu input tidak berubah, mengira hasilnya, lalu memulangkan satu baris. Anak-anak tidak menambah terus pada tatasusunan induk. Ini mengikut pendekatan dalam panduan bahasa yang mengumpulkan hasil anak dalam badan kumpulan tugas. Lihat Keserentakan.
Pada mulanya, pengumpul menghantar bilangan anak yang terhad. Setiap baris yang selesai membuka satu tempat untuk input lain. Oleh itu, pengiraan yang gagal memajukan kelompok dengan cara yang sama seperti pengiraan yang berjaya: kedua-duanya memulangkan baris biasa dan pengumpulan diteruskan.
Foundation activeProcessorCount melaporkan bilangan teras pemprosesan aktif yang tersedia pada komputer. Contoh ini mengehadkan bilangan itu kepada empat sebagai pilihan penjadualan untuk tujuan ilustrasi. Had itu bukan optimum yang diukur atau jaminan bahawa empat pengiraan berjalan serentak. Lihat dokumentasi activeProcessorCount Apple.
Had keserentakan mengehadkan bilangan tugas anak yang sedang berjalan, manakala tatasusunan hasil tetap berkembang mengikut bilangan input. Ini sesuai untuk kelompok desktop yang kecil. Untuk kuasa dua yang sangat ringkas ini, contoh tersebut menunjukkan pengendalian hasil; ia tidak mendakwa prestasi lebih baik daripada gelung berjujukan.
Pengawal input kosong memulangkan tatasusunan kosong. Had keserentakan sifar atau negatif dihadkan kepada satu. Nilai input yang berulang kekal berasingan kerana identitinya datang daripada kedudukan, jadi dua input yang sama tetap menghasilkan dua baris.
Apakah yang berlaku apabila tugas induk dibatalkan?
Pembatalan Swift bersifat kooperatif. Pembatalan tugas induk menyebarkan pembatalan kepada anak berstrukturnya, tetapi anak mesti mengesan isyarat itu dan bertindak balas. Dokumentasi TaskGroup Apple menerangkan penyebaran pembatalan serta keperluan untuk pengendalian secara kooperatif.
Di sini, evaluate memanggil Task.checkCancellation() sebelum membuat pengiraan. Apple mendokumenkan bahawa kaedah ini melontarkan CancellationError apabila pembatalan dikesan; pembungkus menangkapnya secara berasingan dan memulangkan baris yang dibatalkan. Lihat checkCancellation().
Sebaik sahaja pengumpul mengesan pembatalan, ia menandakan input yang belum dihantar sebagai dibatalkan. Anak yang telah dihantar tetap diselesaikan. Hasilnya yang berjaya kekal dalam laporan, dan anak-anak yang mengesan pembatalan memulangkan baris yang dibatalkan.
Pembatalan mungkin tiba selepas anak melakukan semakan. Anak itu masih boleh selesai dengan jayanya, dan kontrak ini menerimanya. Jika pengiraan menjadi gelung yang panjang, tambahkan semakan pembatalan pada sela yang sesuai dalam pengiraan itu. Kumpulan masih menunggu semua anak yang telah dihantar selesai sebelum memulangkan hasil, seperti yang didokumenkan untuk TaskGroup.
Bilakah anda patut menggunakan kumpulan tugas yang melontarkan ralat?
Jika anak sedia ada sudah melontarkan ralat dan anda mahu mengekalkan antara muka itu, kumpulan tugas yang melontarkan ralat juga menyokong pengumpulan hasil individu. Panggil nextResult() berulang kali dan pilih antara setiap kejayaan atau kegagalan yang dipulangkan sehingga hasil pilihan menjadi kosong. Tidak seperti next(), kaedah ini memulangkan nilai Result dan bukannya melontarkan ralat anak kepada pemanggil. Apple mendokumenkan tingkah laku susunan penyelesaiannya dalam nextResult().
Bagi reka bentuk itu, sertakan identiti input dalam nilai yang berjaya dan dalam sebarang ralat yang perlu dikaitkan dengan input tertentu. Ralat aritmetik tanpa butiran tidak mengenal pasti input yang mana antara beberapa input telah menghasilkannya. Pilih juga dasar pembatalan yang jelas dan bukannya menganggap pembatalan sebagai kegagalan pengiraan biasa.
Gunakan pendekatan yang memulangkan hasil apabila keperluan utama ialah laporan lengkap bagi setiap input. Gunakan kumpulan yang melontarkan ralat apabila tingkah laku kelompok yang dikehendaki ialah menyebarkan ralat keluar daripada kumpulan, atau apabila menggunakan operasi melontarkan ralat sedia ada melalui nextResult() lebih mudah.
Bagi tugas pengiraan setempat ini, kontrak utamanya mudah: kekalkan setiap nilai yang berjaya, rekodkan setiap input yang gagal, dan teruskan pengumpulan. Membawa indeks input dalam setiap hasil menjadikan laporan akhir berguna walaupun pengiraan selesai dalam susunan yang sukar dijangka.
Sumber
- withThrowingTaskGroup(of:returning:isolation:body:) — Dokumentasi Pembangun Apple.
- TaskGroup — Dokumentasi Pembangun Apple.
- Keserentakan — Bahasa Pengaturcaraan Swift, projek Swift.
- multipliedReportingOverflow(by:) — Dokumentasi Pembangun Apple.
- Panduan Bermula — Swift.org.
- activeProcessorCount — Dokumentasi Pembangun Apple.
- checkCancellation() — Dokumentasi Pembangun Apple.
- nextResult() — Dokumentasi Pembangun Apple.