Untuk mempertahankan error bertipe dalam perhitungan Swift konkuren, kembalikan Result bertipe dari setiap tugas anak, lalu buka nilainya setelah pengumpulan. Contoh Mac ini menghitung jumlah piksel persegi panjang sintetis sambil mempertahankan urutan input, kegagalan eksplisit, dan kontrak batch yang sepenuhnya berhasil atau gagal.
Mengapa menggunakan hasil bertipe pada batas grup tugas?
Misalkan Anda sedang membuat pratinjau grafis desktop yang menghitung jumlah piksel untuk beberapa persegi panjang. Setiap perhitungan dapat menolak dimensi yang tidak valid atau melaporkan overflow bilangan bulat. Pemanggil memerlukan array lengkap yang berurutan atau error perhitungan tertentu agar dapat menangani setiap kegagalan yang didukung secara eksplisit.
Swift memperkenalkan typed throws di Swift 6.0. Deklarasi seperti throws(CalculationError) membatasi nilai yang dilempar fungsi pada tipe error tersebut. Ini berguna untuk kontrak perhitungan kecil dan tertutup yang implementasi serta pemanggilnya berkembang bersama. Lihat SE-0413: Typed throws.
Batas API konkurensi penting. Signature withThrowingTaskGroup yang didokumentasikan Apple memberikan ThrowingTaskGroup<ChildTaskResult, any Error> kepada body-nya. Tipe grup yang tampak generik itu tidak menyediakan parameter error domain konkret melalui factory tersebut. Karena itu, menambahkan typed throws pada perhitungan saja tidak menetapkan tipe error konkret di seluruh API grup ini. Lihat withThrowingTaskGroup.
Salah satu cara yang didukung adalah menggunakan withTaskGroup dan membuat setiap tugas anak mengembalikan Result<Int, CalculationError>. Tugas selesai secara normal dengan nilai yang merepresentasikan keberhasilan atau kegagalan. Setelah mengumpulkan nilai-nilai tersebut, fungsi batch memanggil get(), yang signature terdokumentasinya adalah throws(Failure). Cara ini memulihkan pelemparan bertipe tanpa cast saat runtime. Lihat dokumentasi Apple tentang Result dan get().
Tetapkan kontrak batch sebelum menulis kode konkurensi
Dalam contoh ini, setiap input berisi lebar dan tinggi bilangan bulat yang diukur dalam piksel. Perhitungannya adalah hasil kali keduanya. Semua data bersifat sintetis dan tetap berada di memori; program tidak merender gambar maupun memuat atau menyimpan data aplikasi.
Kebijakan berikut adalah pilihan desain untuk contoh ini. Kebijakan ini membuat nilai yang dikembalikan dan error yang dipilih dapat diprediksi:
| Situasi | Perilaku batch |
|---|---|
| Semua persegi panjang valid | Kembalikan jumlah piksel dalam urutan input |
| Lebar atau tinggi bernilai nol atau negatif | Laporkan persegi panjang tersebut sebagai tidak valid |
Hasil kali tidak muat dalam Int |
Laporkan overflow untuk persegi panjang tersebut |
| Beberapa persegi panjang gagal | Laporkan kegagalan dengan indeks input terendah |
| Pembatalan terdeteksi pada titik pemeriksaan batch | Laporkan .cancelled |
| Array input kosong | Kembalikan array kosong, kecuali pembatalan terdeteksi |
Memilih indeks input terendah mengharuskan kita mengumpulkan hasil setiap tugas anak sebelum memilih kegagalan perhitungan. Swift tidak menjamin urutan penjadwalan grup tugas, dan urutan penyelesaian dapat berbeda dari urutan pengiriman. Menyertakan indeks pada setiap hasil memungkinkan pengumpul memulihkan urutan yang diinginkan. Lihat TaskGroup.
Kebijakan ini cocok untuk batch pratinjau kecil yang membutuhkan umpan balik konsisten. Kebijakan ini sengaja menunggu semua perhitungan selesai. Desain yang melaporkan kegagalan pertama yang teramati selama eksekusi mengambil kompromi berbeda dan dapat memilih persegi panjang yang berbeda pada setiap proses.
Buat contoh lokal lengkap di Mac Anda
Gunakan toolchain Swift 6 atau yang lebih baru. Di Terminal, periksa dengan swift --version. Swift.org mendokumentasikan pemeriksaan tersebut di Getting Started dan menyediakan petunjuk instalasi Mac di Install Swift — macOS.
Buat paket executable kecil menggunakan alur kerja yang didokumentasikan pada halaman instalasi:
mkdir TypedCalculations
cd TypedCalculations
swift package init --name TypedCalculations --type executable
Ganti file entry Swift yang dibuat di dalam Sources dengan tiga blok kode berikut, secara berurutan, sebagai satu file. Gunakan hanya satu entry point executable. Contoh ini memakai isolasi aktor default biasa dari paket. Proyek yang dikonfigurasi dengan isolasi main actor default perlu menempatkan tipe dan fungsi perhitungannya di luar isolasi tersebut.
Definisikan input dan kegagalan konkret
Enum error mempertahankan informasi terstruktur: case menunjukkan masalahnya, dan indeks terkait menunjukkan inputnya. Protokol LocalizedError dari Foundation menyediakan errorDescription opsional untuk ditampilkan. Pesan bahasa Inggris di bawah adalah contoh string; konformansi ke protokol tidak menerjemahkannya secara otomatis. Lihat LocalizedError.
import Foundation
struct Rectangle: Sendable {
let width: Int
let height: Int
}
enum CalculationError: Error, Sendable, LocalizedError {
case invalidDimensions(index: Int)
case overflow(index: Int)
case cancelled
var errorDescription: String? {
switch self {
case .invalidDimensions(let index):
return "Rectangle \(index + 1) needs positive dimensions."
case .overflow(let index):
return "Rectangle \(index + 1) has too many pixels for Int."
case .cancelled:
return "Calculation cancelled."
}
}
}
struct IndexedOutcome: Sendable {
let index: Int
let result: Result<Int, CalculationError>
}
Hitung, kumpulkan, dan pulihkan pelemparan bertipe
Aritmetika menggunakan multipliedReportingOverflow(by:). Apple mendokumentasikan bahwa partialValue-nya adalah hasil kali penuh hanya jika overflow bernilai false; jika tidak, nilainya berisi hasil kali yang terpotong. Guard di bawah mencegah nilai terpotong itu dianggap sebagai jumlah piksel yang berhasil dihitung. Lihat multipliedReportingOverflow(by:).
func pixelCount(
for rectangle: Rectangle,
index: Int
) throws(CalculationError) -> Int {
if Task.isCancelled {
throw .cancelled
}
guard rectangle.width > 0, rectangle.height > 0 else {
throw .invalidDimensions(index: index)
}
let product = rectangle.width.multipliedReportingOverflow(
by: rectangle.height
)
guard !product.overflow else {
throw .overflow(index: index)
}
return product.partialValue
}
func calculateBatch(
_ rectangles: [Rectangle]
) async throws(CalculationError) -> [Int] {
if Task.isCancelled {
throw .cancelled
}
let outcomes: [IndexedOutcome] = await withTaskGroup(
of: IndexedOutcome.self,
returning: [IndexedOutcome].self
) { group in
for (index, rectangle) in rectangles.enumerated() {
group.addTask {
let result = Result<Int, CalculationError> {
() throws(CalculationError) -> Int in
try pixelCount(for: rectangle, index: index)
}
return IndexedOutcome(index: index, result: result)
}
}
var collected: [IndexedOutcome] = []
for await outcome in group {
collected.append(outcome)
}
return collected.sorted { $0.index < $1.index }
}
if Task.isCancelled {
throw .cancelled
}
var counts: [Int] = []
counts.reserveCapacity(outcomes.count)
for outcome in outcomes {
counts.append(try outcome.result.get())
}
return counts
}
Anotasi eksplisit throws(CalculationError) pada closure Result membuat kontrak error-nya terlihat, alih-alih mengandalkan inferensi closure. SE-0413 mencatat bahwa perubahan inferensi typed throws menyeluruh yang semula direncanakan tidak diimplementasikan di Swift 6.0. Lihat catatan implementasi di Typed throws.
Hanya pengumpul induk yang menambahkan nilai ke collected. Setiap tugas anak menerima persegi panjang dan indeks, lalu mengembalikan hasil Sendable miliknya sendiri. Ini sesuai dengan persyaratan grup tugas bahwa nilai hasil tugas anak harus Sendable; body grup yang tidak melempar mengumpulkannya dengan for await. Lihat withTaskGroup.
Tambahkan entry point executable
Blok terakhir ini menyediakan semua input contoh dan memanggil fungsi batch. Penetapan eksplisit di dalam catch juga memeriksa kontrak API yang dimaksud saat kompilasi: nilai yang ditangkap harus berupa CalculationError.
@main
struct TypedCalculations {
static func main() async {
let examples: [(name: String, rectangles: [Rectangle])] = [
(
name: "Valid",
rectangles: [
Rectangle(width: 640, height: 480),
Rectangle(width: 320, height: 200)
]
),
(
name: "Multiple failures",
rectangles: [
Rectangle(width: 0, height: 20),
Rectangle(width: Int.max, height: 2)
]
),
(
name: "Overflow",
rectangles: [
Rectangle(width: Int.max, height: 2)
]
),
(name: "Empty", rectangles: [])
]
for example in examples {
do {
let counts = try await calculateBatch(example.rectangles)
print("\(example.name): \(counts)")
} catch {
let failure: CalculationError = error
print("\(example.name): \(failure.errorDescription ?? "Calculation failed.")")
}
}
}
}
Jalankan executable dari direktori paket:
swift run TypedCalculations
Output aplikasi yang diharapkan, tanpa pesan build, ditampilkan di bawah. Tidak ada compiler Swift yang tersedia di lingkungan riset, jadi program ini diperiksa berdasarkan dokumentasi API yang diperoleh, tetapi tidak dikompilasi atau dijalankan di sana.
Valid: [307200, 64000]
Multiple failures: Rectangle 1 needs positive dimensions.
Overflow: Rectangle 1 has too many pixels for Int.
Empty: []
Jumlah yang valid berasal langsung dari input ilustratif: 640 dikalikan 480 adalah 307.200, dan 320 dikalikan 200 adalah 64.000. Dalam contoh beberapa kegagalan, persegi panjang pertama yang dipilih karena hasil diperiksa dalam urutan input, meskipun tugas anak kedua selesai lebih dahulu.
Bagaimana perilaku pembatalan dan hasil parsial?
Pembatalan di Swift bersifat kooperatif. Sebuah tugas harus mendeteksi pembatalan dan meresponsnya; permintaan pembatalan tidak otomatis menghentikan perhitungannya. Grup tugas terstruktur juga menunggu tugas anaknya selesai sebelum kembali. Lihat bab Concurrency dalam panduan bahasa Swift dan dokumentasi Apple tentang withTaskGroup.
Di sini, Task.isCancelled diperiksa sebelum pengiriman, di dalam setiap perhitungan, dan setelah pengumpulan. Saat titik pemeriksaan mendeteksi pembatalan, kode merepresentasikannya menggunakan case .cancelled yang sudah ada. Membaca nilai Boolean menghindari penambahan tipe error lain yang dilempar ke dalam kontrak tertutup ini. Apple mendokumentasikan properti tersebut di Task.isCancelled.
Pemeriksaan setelah pengumpulan membuat pembatalan batch yang terdeteksi lebih diutamakan daripada kegagalan persegi panjang. Pemeriksaan ini adalah titik pemeriksaan, bukan jaminan atomik: pembatalan yang diminta setelah pemeriksaan terakhir mungkin tidak memengaruhi hasil yang dikembalikan. Untuk perhitungan lebih panjang yang terdiri atas pekerjaan berulang, tambahkan pemeriksaan di dalam pekerjaan tersebut agar pembatalan dapat dideteksi lebih cepat.
Tugas anak yang gagal tidak memicu pembatalan dalam implementasi ini. Tugas tersebut mengembalikan nilai Result.failure normal, dan pengumpul melanjutkan prosesnya. Jika semua tugas anak selesai dengan sukses, pemanggil menerima setiap jumlah. Jika proses membuka nilai menemukan kegagalan, fungsi akan melempar error dan array lokal counts yang baru dirakit sebagian tidak akan dikembalikan.
Jika antarmuka desktop justru memerlukan status berhasil atau gagal untuk setiap persegi panjang, ubah kontrak batch publik agar mengembalikan hasil yang berurutan. Pertahankan setiap kegagalan bersama indeksnya. Perilaku yang terlihat oleh pengguna itu berbeda dari array yang seluruhnya berhasil atau gagal yang diimplementasikan di sini.
Diagnosis tipe error yang hilang tanpa menyembunyikan kegagalan
Jika kode yang Anda adaptasi tidak lagi dapat dikompilasi, telusuri tipe pada setiap batas dengan urutan berikut:
- Pastikan perhitungan mendeklarasikan
throws(CalculationError). - Pastikan nilai tugas anak bertipe
Result<Int, CalculationError>, bukan result dengan kegagalan bertipeany Error. - Pertahankan anotasi typed throws eksplisit pada closure jika inferensi memperluas tipe error-nya.
- Gunakan
withTaskGroupyang tidak melempar untuk mengirim nilai result ini. - Buka nilainya dengan
get()di dalam fungsi batch bertipe. - Periksa apakah operasi throwing yang baru ditambahkan memperkenalkan tipe error lain.
Cast paksa tidak dapat membuktikan kontrak ini. Begitu pula membuang hasil yang gagal atau menggantinya dengan nol akan mengubah arti perhitungan. Pertahankan kegagalan sebagai nilai hingga kebijakan batch yang Anda pilih menentukan cara melaporkannya.
Untuk perkalian kecil ini, konkurensi adalah demonstrasi arsitektur; tidak ada klaim peningkatan performa. Hasil yang berguna adalah kontrak yang jelas: jumlah yang berhasil mempertahankan urutan input, beberapa kegagalan memiliki aturan pemilihan yang deterministik, dan pemanggil menerima enum error konkret yang dapat langsung ditangani.
Sumber
Typed throws — SE-0413 — Swift Project.
withThrowingTaskGroup(of:returning:isolation:body:) — Apple.
Result — Apple.
get() — Apple.
TaskGroup — Apple.
Getting Started — Swift.org.
Install Swift — macOS — Swift.org.
LocalizedError — Apple.
multipliedReportingOverflow(by:) — Apple.
Concurrency — The Swift Programming Language — Swift Project.
isCancelled — Apple.