Untuk app Mac yang harus menyelesaikan perhitungan asinkron lokal sesuai urutan pengiriman, gunakan metode pengiriman sinkron pada aktor utama yang merangkai setiap tugas setelah pendahulunya. Isolasi aktor saja tidak memberikan jaminan urutan tersebut.
Panduan ini membuat antrean kecil untuk perhitungan bilangan bulat sintetis. Panduan ini menetapkan dengan tepat kapan pengiriman terjadi, mempertahankan urutan melewati titik suspensi, mengembalikan hasil setiap perhitungan, dan memungkinkan pekerjaan berikutnya berlanjut setelah pekerjaan sebelumnya gagal.
Apa arti urutan pengiriman yang ketat?
Misalkan kalkulator desktop mengirim pekerjaan A, B, dan C. Kontrak yang diwajibkan adalah: perhitungan A selesai sebelum perhitungan B dimulai, dan B selesai sebelum C dimulai. Menunggu tiga tugas yang dimulai secara terpisah dalam urutan alfabetis hanya akan mengurutkan pengamatan atas hasilnya.
Batas pengiriman itu penting. Dalam implementasi ini, pengiriman terjadi saat metode submit yang diisolasi pada aktor utama dijalankan dan memasang tautan berikutnya dalam rantai. Pemanggilan sinkron berturut-turut dari satu konteks aktor utama menetapkan urutan A, B, C tanpa suspensi di antaranya.
Jika tugas-tugas independen meminta pengiriman secara bersamaan, urutan kedatangannya tidak ditentukan. Antrean mempertahankan urutan saat metode pengirimannya menerima tugas-tugas tersebut. Jika aplikasi Anda memerlukan urutan tertentu di antara produsen tersebut, tetapkan urutan itu sebelum pengiriman—misalnya, dengan mengumpulkan sekumpulan terbatas ke dalam larik yang diurutkan secara eksplisit.
Desain ini menggunakan aktor utama untuk langkah pencatatan singkat. Apple mendefinisikan MainActor sebagai aktor singleton yang executornya setara dengan antrean pengiriman utama. Perhitungan sebenarnya di bawah ini dilakukan oleh aktor pekerja terpisah. Dokumentasi MainActor
Mengapa aktor atau antrean operasi serial saja tidak cukup?
Aktor menjalankan pekerjaan yang diisolasi pada aktor tersebut satu bagian dalam satu waktu, tetapi metode asinkron dapat tersuspensi dan memungkinkan pemanggilan lain berjalan. Desain aktor Swift juga menyatakan secara eksplisit bahwa tugas yang menunggu aktor tidak dijamin berjalan sesuai urutan awalnya menunggu. Karena itu, menempatkan seluruh perhitungan asinkron di dalam aktor tidak dengan sendirinya menjamin perilaku bahwa satu pekerjaan selesai sebelum pekerjaan berikutnya dimulai. SE-0306: Aktor
Membuat instans Task terpisah juga tidak memberikan dependensi di antara tugas-tugas itu. Tugas dapat mulai berjalan setelah dibuat; menyimpan handle-nya memungkinkan Anda menunggunya atau membatalkannya, tetapi tidak menetapkan urutan eksekusi. Dokumentasi Task
Foundation menyediakan mekanisme pengurutan yang berbeda. OperationQueue menjadwalkan berdasarkan kesiapan, prioritas, dan dependensi; Apple menyarankan penggunaan dependensi jika urutan eksekusi tertentu diperlukan. Mengatur maxConcurrentOperationCount ke 1 membatasi konkurensi, tetapi persyaratan urutan tetap memerlukan dependensi eksplisit. Dokumentasi OperationQueue
Pilih mekanisme sesuai kebutuhan aplikasi Anda:
| Situasi | Pendekatan yang sesuai | Apa yang menetapkan urutan? |
|---|---|---|
| Satu kumpulan pekerjaan yang sudah diketahui dalam satu fungsi asinkron | Perulangan yang menunggu setiap perhitungan sebelum melanjutkan | Alur kontrol berurutan |
| Pekerjaan datang seiring waktu dari tindakan desktop | Metode pengiriman sinkron dengan rantai tugas | Setiap perhitungan menunggu gerbang penyelesaian pendahulunya |
| Operasi Foundation yang sudah ada | Dependensi yang dikonfigurasi sebelum dimasukkan ke antrean | Setiap operasi bergantung pada status selesai operasi sebelumnya |
Rantai tugas berguna di sini karena pengiriman segera mengembalikan handle sementara perhitungan berjalan kemudian. Untuk kumpulan tetap tanpa pekerjaan yang datang belakangan, perulangan berurutan biasanya membutuhkan lebih sedikit kode.
Buat antrean yang menunggu perhitungan asinkron selesai sepenuhnya
Antrean menyimpan tugas penyelesaian privat yang tidak melemparkan galat bernama tail. Setiap pengiriman menangkap elemen terakhir saat ini, membuat tugas perhitungan yang menunggunya, lalu mengganti elemen terakhir dengan tugas baru yang mengamati penyelesaian perhitungan.
Tugas penyelesaian menunggu result, yang merepresentasikan keberhasilan maupun kegagalan setelah suatu tugas selesai. Dengan begitu, perhitungan yang gagal tetap dapat membuka jalan bagi pekerjaan berikutnya tanpa membuang galat dari handle perhitungan itu sendiri. Pemanggil tetap dapat memperoleh galat tersebut melalui handle. Dokumentasi hasil Task
Berikut demonstrasi baris perintah lengkap untuk Mac dengan toolchain Swift 6. Simpan sebagai OrderedDemo.swift. Kode sumber telah diperiksa, tetapi tidak dapat dikompilasi di lingkungan riset ini karena compiler Swift tidak tersedia.
import Foundation
@MainActor
final class OrderedIntQueue {
private var tail: Task<Void, Never>?
func submit(
_ operation: @escaping @Sendable () async throws -> Int
) -> Task<Int, Error> {
let predecessor = tail
let job = Task<Int, Error> {
if let predecessor {
await predecessor.value
}
try Task.checkCancellation()
return try await operation()
}
tail = Task<Void, Never> {
_ = await job.result
}
return job
}
}
enum CalculationError: Error {
case inputOutOfRange
}
actor CalculationWorker {
private var events: [String] = []
func square(_ input: Int, label: String) async throws -> Int {
events.append("start \(label)")
guard (-1000...1000).contains(input) else {
throw CalculationError.inputOutOfRange
}
await Task.yield()
try Task.checkCancellation()
let answer = input * input
events.append("finish \(label)")
return answer
}
func trace() -> [String] {
events
}
}
@main
struct OrderedDemo {
@MainActor
static func main() async {
let queue = OrderedIntQueue()
let worker = CalculationWorker()
let a = queue.submit {
try await worker.square(3, label: "A")
}
let b = queue.submit {
try await worker.square(4, label: "B")
}
let c = queue.submit {
try await worker.square(5, label: "C")
}
var answers: [Int] = []
for job in [a, b, c] {
switch await job.result {
case .success(let answer):
answers.append(answer)
case .failure(let error):
print("Calculation failed: \(error)")
}
}
let events = await worker.trace()
precondition(answers == [9, 16, 25])
precondition(events == [
"start A", "finish A",
"start B", "finish B",
"start C", "finish C"
])
print(events.joined(separator: "\n"))
print("Answers: \(answers)")
}
}
Kompilasi dan jalankan program yang disimpan dari direktori tempat berkas berada di Terminal:
swiftc -swift-version 6 -parse-as-library OrderedDemo.swift -o OrderedDemo
./OrderedDemo
Keluaran yang diharapkan mengikuti masukan dan rantai dependensi dalam contoh:
start A
finish A
start B
finish B
start C
finish C
Answers: [9, 16, 25]
Pernyataan assertion memeriksa nilai yang dikembalikan dan batas-batas perhitungan. Sekadar mencetak hasil sambil menunggu handle sesuai urutan pengiriman tidak membuktikan bahwa perhitungan berjalan dalam urutan tersebut.
Pekerja sengaja memanggil Task.yield() agar perhitungan memiliki kesempatan untuk tersuspensi. Yield memungkinkan tugas lain berjalan, meskipun executor dapat langsung melanjutkan tugas yang sama. Karena itu, pengurutan antrean berasal dari dependensinya, bukan dari asumsi bahwa yield akan mengalihkan eksekusi ke tugas lain. Dokumentasi yield Task
Mengapa rantai ini mempertahankan urutan saat tersuspensi?
Ada tiga hal yang memberikan jaminan ini. Pertama, submit tidak memiliki titik suspensi, sehingga pengiriman lain pada aktor utama tidak dapat menyela saat metode ini menangkap dan mengganti tail. Swift mendokumentasikan bahwa kode yang diisolasi pada aktor tanpa titik suspensi berjalan tanpa disela oleh tugas aktor lain. Konkurensi: Aktor
Kedua, setiap pekerjaan menunggu pendahulu yang ditangkapnya sebelum memanggil closure perhitungan. Mengakses value milik tugas yang tidak melemparkan galat akan menunggu hingga tugas tersebut selesai. Pekerjaan berikutnya boleh menjalankan langkah penunggu lebih awal, tetapi tidak dapat memulai perhitungannya lebih awal. Dokumentasi value untuk Task yang tidak melemparkan galat
Ketiga, gerbang pendahulu baru selesai setelah pekerjaan sebelumnya menghasilkan nilai atau galat. Menelusuri tautan ke belakang menetapkan urutan untuk setiap pengiriman yang diterima: C menunggu gerbang penyelesaian B, yang menunggu B, yang menunggu gerbang penyelesaian A.
Jaminan pengurutan ini berlaku untuk pekerjaan yang ditunggu di dalam setiap closure yang dikirimkan. Jika closure meluncurkan tugas tak terstruktur lain lalu kembali tanpa menunggunya, pekerjaan tambahan tersebut dapat terus berjalan setelah closure selesai. Sertakan setiap langkah perhitungan yang diperlukan dalam operasi yang ditunggu sebelum kembali.
Untuk integrasi desktop, simpan satu instans antrean dalam controller atau model aktor utama yang menangani tindakan terkait. Panggil submit langsung dari konteks tersebut, lalu gunakan handle yang dikembalikan untuk mengamati penyelesaian. Membuat tugas independen terlebih dahulu lalu mengirimkannya hanya setelah tugas mulai berjalan akan memindahkan batas pengiriman ke saat tugas itu akhirnya dijalankan.
Bagaimana seharusnya galat dan pembatalan ditangani?
Implementasi ini mengikuti kebijakan lanjutkan setelah kegagalan. Pekerjaan yang gagal tetap menyimpan galatnya, gerbang penyelesaiannya selesai, dan pekerjaan berikutnya yang diterima menjadi layak untuk dihitung. Demonstrasi memeriksa result dari setiap handle alih-alih menghentikan proses dan membuang hasil yang tersisa setelah galat pertama.
Untuk pemeriksaan kegagalan sederhana, ubah masukan B dari 4 menjadi 1001. Pekerja menolaknya sebelum perkalian. Perbarui jawaban yang diharapkan menjadi [9, 25] dan jejak yang diharapkan menjadi start A, finish A, start B, start C, finish C. Program seharusnya mencetak kegagalan dan tetap menghitung C. Batas masukan ini adalah kontrak ilustratif yang dipilih agar aritmetika tetap sederhana.
Pembatalan bersifat kooperatif. Antrean memeriksa pembatalan setelah menunggu pendahulunya, dan pekerja memeriksanya lagi setelah melakukan yield. Task.checkCancellation() melempar CancellationError saat pembatalan diminta. Dokumentasi checkCancellation Task
Untuk memeriksa pembatalan sebelum eksekusi, panggil b.cancel() segera setelah mengirim C dan sebelum perulangan hasil. Dengan pengiriman aktor utama tanpa suspensi ini, B belum mungkin memasuki perhitungannya. Sesuaikan jawaban yang diharapkan menjadi [9, 25] dan hapus kedua entri B dari jejak yang diharapkan. B tetap menunggu gerbang penyelesaian A sebelum melaporkan pembatalan; setelah itu C melanjutkan.
Jaga agar gerbang penyelesaian tetap privat. Gerbang tersebut merepresentasikan pencatatan rantai, sedangkan handle yang dikembalikan merepresentasikan perhitungan individual. Membatalkan pekerjaan tidak berarti perhitungan sebelumnya harus dihentikan atau perhitungan berikutnya harus dimulai saat pekerjaan sebelumnya masih berjalan.
Jika aplikasi Anda justru harus menghentikan seluruh kumpulan setelah satu kegagalan, diperlukan kebijakan kumpulan yang eksplisit. Mengubah gerbang agar meneruskan galat akan mengubah pekerjaan berikutnya yang dijalankan dan cara hasilnya harus dilaporkan.
Kapan sebaiknya menggunakan dependensi Foundation?
Jika aplikasi desktop sudah merepresentasikan pekerjaan sebagai objek Operation Foundation, tetapkan dependensi dari setiap operasi ke pendahulunya sebelum memasukkannya ke antrean. Foundation menganggap dependensi terpenuhi saat pendahulu selesai, baik berhasil maupun gagal; perilaku yang bergantung pada keberhasilan memerlukan penanganan hasil terpisah. Dokumentasi Operation
Berhati-hatilah saat menjembatani fungsi asinkron Swift ke model tersebut. Operasi sinkron yang memulai Task lalu langsung kembali tidak merepresentasikan seluruh masa hidup tugas. Operasi asinkron khusus harus mengelola eksekusi dan status selesai secara akurat, termasuk notifikasi yang diwajibkan, agar operasi yang bergantung padanya menunggu pekerjaan yang dimaksud. Apple mendokumentasikan tanggung jawab tersebut di bagian operasi asinkron dan pelaporan status selesai. Dokumentasi siklus hidup Operation
Untuk kalkulator asinkron lokal yang dibuat di sini, rantai tugas membuat kontraknya jelas: terima pengiriman secara sinkron, tunggu penyelesaian pendahulu, jalankan seluruh perhitungan, dan tampilkan hasilnya. Pertahankan batas-batas ini saat menghubungkan antrean ke tindakan desktop, dan validasi eksekusi dengan jejak mulai dan selesai serta nilai yang dikembalikan.
Sumber
- MainActor — Apple
- SE-0306: Aktor — Swift
- Task — Apple
- OperationQueue — Apple
- result — Apple
- yield() — Apple
- Konkurensi — Swift
- value: Tugas yang tidak melemparkan galat — Apple
- checkCancellation() — Apple
- Operation — Apple