Gunakan grup tugas Swift untuk mengumpulkan perhitungan yang bersaing sesuai urutan penyelesaiannya, abaikan nil, dan batalkan tugas anak yang tersisa saat sebuah nilai tiba. Contoh baris perintah Mac ini menemukan tata letak persegi panjang yang dapat digunakan untuk ubin sintetis dan menjelaskan kapan hasilnya benar-benar dapat dikembalikan.
Tentukan apa yang dianggap sebagai hasil berguna pertama
Misalkan sebuah alat tata letak desktop kecil perlu menyusun 221 ubin sintetis menjadi bentuk persegi panjang. Beberapa perhitungan lokal mencari jumlah baris yang mungkin. Faktor nontrivial apa pun dapat diterima: 13 baris dan 17 kolom bisa digunakan, begitu pula 17 baris dan 13 kolom. Tugas pembaca adalah menerima perhitungan valid mana pun yang selesai lebih dulu.
Ini adalah kontrak urutan penyelesaian. Kontrak ini tidak memilih faktor terkecil, persegi panjang yang paling seimbang, atau perhitungan yang tercantum lebih dahulu. Apple mendokumentasikan bahwa hasil grup tugas berturut-turut tiba sesuai urutan penyelesaian, terlepas dari urutan penyisipan. TaskGroup.next(isolation:)
Tentukan perilakunya sebelum menulis helper:
| Situasi | Perilaku yang diwajibkan |
|---|---|
Sebuah perhitungan selesai dengan nil |
Terus menunggu perhitungan lain. |
| Sebuah perhitungan selesai dengan nilai | Pilih nilai tersebut dan minta pembatalan pekerjaan yang tersisa. |
Semua perhitungan menghasilkan nil |
Kembalikan nil. |
| Daftar perhitungan kosong | Kembalikan nil. |
| Pembatalan dari induk terdeteksi | Berhenti memilih hasil dan kembalikan nil. |
Baris terakhir merupakan pilihan API yang disengaja untuk contoh ini. Di sini, nil berarti pemanggilan tersebut tidak menyediakan nilai yang dapat digunakan; status ini tidak membedakan pencarian yang gagal dari pembatalan. Jika pemanggil membutuhkan pembedaan itu, gunakan tipe hasil eksplisit atau kontrak yang dapat melemparkan galat.
Buat contoh lokal lengkap di Mac Anda
Gunakan toolchain Swift 6 atau yang lebih baru. Di Terminal, periksa dengan swift --version. Jika Swift tidak tersedia, ikuti petunjuk resmi Install Swift — macOS. Program ini menggunakan Foundation dan API konkurensi Swift tanpa dependensi paket eksternal.
Buat paket executable baru dengan perintah berikut. Langkah ini mengikuti alur kerja pembuatan dan eksekusi paket yang didokumentasikan oleh Swift.org. Build a Command-line Tool
mkdir FirstNonNilDemo
cd FirstNonNilDemo
swift package init --name FirstNonNilDemo --type executable
Ganti sumber executable yang dibuat dengan program lengkap di bawah ini. Bergantung pada tata letak yang dibuat, namanya mungkin Sources/FirstNonNilDemo/FirstNonNilDemo.swift atau Sources/main.swift. Gunakan satu berkas entry point bernama FirstNonNilDemo.swift; jika templat membuat main.swift, ganti nama berkas itu sebelum menambahkan program @main. Panduan Swift mendokumentasikan bahwa program menggunakan main.swift atau entry point @main. Build a Command-line Tool
import Foundation
func firstNonNil<Value: Sendable>(
_ calculations: [@Sendable () async -> Value?]
) async -> Value? {
guard !Task.isCancelled else { return nil }
return await withTaskGroup(
of: Value?.self,
returning: Value?.self
) { group in
defer { group.cancelAll() }
for calculation in calculations {
let added = group.addTaskUnlessCancelled {
guard !Task.isCancelled else { return nil }
return await calculation()
}
guard added else { break }
}
for await candidate in group {
guard !Task.isCancelled else { return nil }
if let value = candidate {
return value
}
}
return nil
}
}
func findFactor(total: Int, range: ClosedRange<Int>) -> Int? {
guard total > 1 else { return nil }
for rows in range {
guard !Task.isCancelled else { return nil }
guard rows > 1, rows < total else { continue }
if total % rows == 0 {
return rows
}
}
return nil
}
@main
struct FirstNonNilDemo {
static func main() async {
let checks: [@Sendable () async -> Int?] = [
{ nil },
{ 42 }
]
let checkResult = await firstNonNil(checks)
precondition(checkResult == 42)
let empty: [@Sendable () async -> Int?] = []
let emptyResult = await firstNonNil(empty)
precondition(emptyResult == nil)
for rawText in [" 221\n", " 223 ", "tiles"] {
let text = rawText.trimmingCharacters(
in: .whitespacesAndNewlines
)
guard let total = Int(text), total > 1 else {
print("Input must be a whole number greater than 1.")
continue
}
let calculations: [@Sendable () async -> Int?] = [
{ findFactor(total: total, range: 2...5) },
{ findFactor(total: total, range: 6...16) },
{ findFactor(total: total, range: 17...30) }
]
if let rows = await firstNonNil(calculations) {
print("\(total) tiles: \(rows) rows x \(total / rows) columns")
} else {
print("\(total) tiles: no factor in the supplied ranges.")
}
}
}
}
Jalankan paket dari direktorinya:
swift run FirstNonNilDemo
Untuk 221, 221 tiles: 13 rows x 17 columns atau 221 tiles: 17 rows x 13 columns sama-sama dapat diterima. Untuk 223, program melaporkan tidak ada faktor dalam rentang yang diberikan. Teks tiles menghasilkan pesan input. Ini adalah hasil yang diharapkan, bukan transkrip eksekusi yang tercatat. Aritmetikanya diperiksa secara terpisah; kompiler Swift tidak tersedia di lingkungan riset, sehingga program Swift ditinjau tetapi tidak dijalankan.
Foundation hanya menangani pembersihan spasi di sekeliling teks. Metode trimmingCharacters(in:) menghapus karakter yang cocok dari kedua ujung; metode ini tidak menghapus karakter di dalam string. Karena itu, 221 menjadi 221, sedangkan 2 21 tetap tidak valid untuk program ini. trimmingCharacters(in:)
Mengapa helper mengabaikan nil tanpa kehilangan hasil berikutnya
Tipe hasil anak dalam grup adalah Value?. Karena itu, setiap anak memberikan nilai yang dapat digunakan atau hasil kosong biasa. Loop for await menerima hasil anak yang telah selesai, dan pengikatan opsional di dalamnya hanya memilih kandidat non-nil.
Ada dua jenis kekosongan yang berbeda jika Anda menggunakan group.next() secara langsung. Tipe kembalian yang didokumentasikan adalah ChildTaskResult?. Dengan hasil anak opsional, tipe ini menjadi Value??: opsional luar menunjukkan apakah hasil anak lain tersedia, dan opsional dalam menunjukkan apakah anak tersebut menemukan nilai. TaskGroup.next(isolation:)
Perbedaan ini menjelaskan kesalahan umum: mengambil satu hasil lalu langsung mengembalikannya. Jika perhitungan yang pertama selesai menghasilkan nil, anak lain mungkin masih memiliki jawaban yang berguna. Loop dalam contoh lengkap terus mengonsumsi hasil sampai menemukan nilai atau grup kehabisan hasil.
Helper menggunakan addTaskUnlessCancelled dan memeriksa nilai Boolean yang dikembalikan. Apple mendokumentasikan bahwa metode ini tidak membuat anak jika grup sudah dibatalkan. Menghentikan loop pengiriman mencegah pekerjaan terus dikirim setelah kondisi tersebut terdeteksi. addTaskUnlessCancelled(priority:operation:)
Perhitungan menangkap jumlah ubin yang tidak berubah dan mengembalikan jawabannya melalui grup. Perhitungan tersebut tidak memperbarui variabel pemenang bersama. Hasil generik harus memenuhi Sendable, sesuai dengan batasan grup tugas yang didokumentasikan. withTaskGroup(of:returning:isolation:body:)
Mengapa memilih pemenang tidak berarti langsung mengembalikannya
Mengembalikan nilai dari closure grup memilih jawaban, tetapi withTaskGroup yang membungkusnya tetap menunggu semua anak selesai. Apple secara eksplisit mendokumentasikan bahwa bahkan anak yang dibatalkan harus selesai sebelum fungsi dikembalikan. withTaskGroup(of:returning:isolation:body:)
Blok defer memanggil group.cancelAll() setiap kali closure keluar. Ini meminta pembatalan perhitungan yang tersisa. Pembatalan tidak menghentikan perhitungan sembarang secara paksa: anak harus bekerja sama dan menyelesaikan lebih awal dengan sendirinya. TaskGroup
Dalam contoh ini, findFactor memeriksa Task.isCancelled sebelum setiap calon pembagi. Setiap unit pekerjaan berukuran kecil, sehingga tersedia kesempatan yang sering untuk berhenti. Untuk perhitungan yang lebih besar, tempatkan pemeriksaan di antara bagian-bagian bermakna yang durasinya terbatas. Pemeriksaan hanya sebelum operasi panjang yang tidak dapat dibagi membuat operasi itu tetap berjalan sampai selesai. Panduan bahasa Swift menjelaskan bahwa pembatalan bersifat kooperatif dan menyebut pengembalian nil sebagai salah satu respons yang didukung. Concurrency
Jika perhitungan yang bersaing tidak pernah selesai dan tidak pernah merespons pembatalan, helper terstruktur ini tidak dapat menjanjikan pengembalian yang cepat. Cara yang didukung adalah memastikan setiap pekerja memiliki batas waktu atau peka terhadap pembatalan. Menambahkan hasil timeout yang bersaing tidak menghapus kewajiban grup untuk menunggu anak-anaknya.
Pembatalan dari induk diperiksa sebelum pengiriman, saat anak mulai, di dalam loop aritmetika, dan sebelum menerima setiap hasil. Pemeriksaan ini mengamati pembatalan pada saat-saat tertentu; pemeriksaan tersebut tidak menciptakan aturan atomik yang selalu mengutamakan pembatalan daripada nilai. Pembatalan yang tiba setelah pemeriksaan terakhir dapat bertepatan dengan pengembalian yang berhasil.
Pilih aturan seleksi yang tepat dan verifikasi batasannya
Contoh sintetis ini memberikan uji keputusan yang berguna: 13 dan 17 sama-sama memenuhi kontrak yang ditetapkan. Jika alat Anda memerlukan jumlah baris yang lebih kecil, menerima hasil mana pun adalah keliru. Urutan penyelesaian hanya berguna ketika semua jawaban yang diterima dapat dipertukarkan untuk tugas pembaca.
| Kebutuhan pemanggil | Pendekatan yang sesuai |
|---|---|
| Nilai apa pun yang dapat diterima saat perhitungan selesai | Gunakan loop grup tugas di atas. |
| Perhitungan berhasil paling awal dalam daftar preferensi tetap | Evaluasi sesuai urutan preferensi, atau simpan indeks dan selesaikan kandidat yang lebih awal sebelum memilih. |
| Hasil terkecil atau dengan skor terbaik | Bandingkan hasil yang relevan sebelum memilih. |
| Beberapa hasil yang sudah diketahui dan diperlukan bersama | Pertimbangkan async let. |
Panduan bahasa Swift menyajikan async let untuk tugas anak yang sudah diketahui dan hasilnya diperlukan kemudian, serta grup tugas untuk mengendalikan anak yang ditambahkan secara dinamis. Menunggu hasil bernama dalam urutan pilihan tidak menerapkan loop seleksi berdasarkan urutan penyelesaian. Concurrency
Gunakan pemeriksaan berikut saat menyesuaikan program:
- Pertahankan assertion daftar kosong: hasilnya harus
nil. - Pertahankan assertion campuran
nildan42: hasilnya harus42terlepas dari urutan penyelesaian. - Uji perhitungan yang semuanya menghasilkan
nil. - Untuk beberapa jawaban valid, pastikan hasil termasuk dalam himpunan valid alih-alih mengharapkan pemenang tertentu.
- Periksa setiap pekerja untuk memastikan kemajuannya terbatas dan terdapat pemeriksaan pembatalan.
- Laporkan pencarian yang tidak berhasil dengan batasan pada rentang yang diberikan; jangan menyimpulkan bahwa tidak ada jawaban di tempat lain.
Tiga pemindaian faktor kecil menunjukkan perilaku, bukan peningkatan kecepatan. Pemindaian berurutan sudah memadai untuk angka ilustratif ini. Gunakan pola perhitungan bersaing ketika tugas sebenarnya diuntungkan oleh penerimaan hasil yang dapat dipertukarkan, lalu ukur beban kerja tersebut sebelum menentukan jumlah pekerjaan yang dijalankan secara bersamaan.
Tetapkan aturan penerimaan dengan jelas, abaikan hasil anak yang kosong, dan pastikan pekerjaan yang tersisa dapat dihentikan. Dengan begitu, alat Mac memiliki kontrak nilai yang dapat diprediksi: hasil berguna pertama yang diberikan grup, kemudian penyelesaian penghentian kooperatif anak-anaknya.
Sumber
- Install Swift — macOS — Swift.org
- Build a Command-line Tool — Swift.org
- TaskGroup — Apple
- withTaskGroup(of:returning:isolation:body:) — Apple
- next(isolation:) — Apple
- addTaskUnlessCancelled(priority:operation:) — Apple
- trimmingCharacters(in:) — Apple
- Concurrency — The Swift Programming Language, Swift.org