Eşzamanlı Swift hesaplamalarında türü belirli hataları korumak için her alt görevden türü belirli bir Result döndürün ve sonuçları topladıktan sonra bunların içindeki değeri açın. Bu Mac örneği, girdi sırasını, açık hata bildirimlerini ve ya hep ya hiç toplu işlem sözleşmesini korurken sentetik dikdörtgenlerin piksel sayılarını hesaplar.
Görev grubu sınırında türü belirli sonuçlar neden kullanılır?
Birden çok dikdörtgenin piksel sayılarını hesaplayan bir masaüstü grafik önizlemesi oluşturduğunuzu varsayalım. Her hesaplama, geçersiz boyutları reddedebilir veya tamsayı taşmasını bildirebilir. Çağıran tarafın, desteklenen her hatayı açıkça ele alabilmesi için sıralı dizinin tamamına ya da belirli bir hesaplama hatasına ihtiyacı vardır.
Swift, türü belirli fırlatmaları Swift 6.0'da kullanıma sundu. throws(CalculationError) gibi bir bildirim, işlevin fırlatabileceği değerleri bu hata türüyle sınırlar. Bu, uygulaması ve çağıranları birlikte geliştirilen, küçük ve kapalı bir hesaplama sözleşmesi için kullanışlıdır. Bkz. SE-0413: Typed throws.
Eşzamanlılık API'sinin sınırı önemlidir. Apple'ın belgelediği withThrowingTaskGroup imzası, gövdesine ThrowingTaskGroup<ChildTaskResult, any Error> sağlar. Türü genel görünen grup türü, bu oluşturucu üzerinden somut bir alana özgü hata parametresi sunmaz. Bu nedenle yalnızca hesaplamanıza türü belirli fırlatmalar eklemek, bu grup API'sinin tamamında somut bir fırlatılan türü oluşturmaz. Bkz. withThrowingTaskGroup.
Desteklenen bir yol, withTaskGroup kullanmak ve her alt görevin Result<Int, CalculationError> döndürmesini sağlamaktır. Görev, başarıyı veya hatayı temsil eden bir değerle normal şekilde tamamlanır. Bu değerleri topladıktan sonra toplu işlem işlevi, belgelenmiş imzası throws(Failure) olan get() işlevini çağırır. Böylece çalışma zamanında tür dönüşümü yapmadan türü belirli fırlatmalara geri dönülür. Apple'ın Result belgelerine ve get() belgelerine bakın.
Eşzamanlılık kodunu yazmadan önce toplu işlem sözleşmesini tanımlayın
Bu örnekte her girdi, piksel cinsinden ölçülen bir tamsayı genişlik ve yükseklik içerir. Hesaplama, bu ikisinin çarpımıdır. Her şey sentetiktir ve bellekte kalır; program görüntü oluşturmaz, uygulama verisi yüklemez veya kaydetmez.
Aşağıdaki ilkeler bu örnek için tasarım tercihleridir. Döndürülen değerleri ve seçilen hatayı öngörülebilir kılarlar:
| Durum | Toplu işlem davranışı |
|---|---|
| Tüm dikdörtgenler geçerli | Piksel sayılarını girdi sırasıyla döndür |
| Genişlik veya yükseklik sıfır ya da negatif | İlgili dikdörtgeni geçersiz olarak bildir |
Çarpım Int türüne sığmıyor |
İlgili dikdörtgen için taşmayı bildir |
| Birden fazla dikdörtgen başarısız | Girdi indeksi en küçük olan hatayı bildir |
| Bir toplu işlem kontrol noktasında iptal algılanıyor | .cancelled bildir |
| Girdi dizisi boş | İptal algılanmadığı sürece boş dizi döndür |
En küçük girdi indeksini seçmek için hesaplama hatasını belirlemeden önce tüm alt görev sonuçlarını toplamak gerekir. Swift, görev gruplarında görevlerin zamanlanma sırasını garanti etmez; tamamlanma sırası da gönderim sırasından farklı olabilir. Her sonuca indeks eklemek, toplayıcının istenen sırayı yeniden oluşturmasını sağlar. Bkz. TaskGroup.
Bu ilke, yinelenebilir geri bildirimin önemli olduğu küçük bir önizleme toplu işlemi için uygundur. Bilerek tüm hesaplamaların tamamlanmasını bekler. Çalışma sırasında gözlemlenen ilk hatayı bildiren bir tasarım farklı bir ödünleşimi tercih eder ve çalıştırmalar arasında farklı bir dikdörtgeni seçebilir.
Mac'inizde eksiksiz bir yerel örnek oluşturun
Swift 6 veya daha yeni bir araç zinciri kurun. Terminal'de swift --version komutuyla sürümü denetleyin. Swift.org bu denetimi Getting Started sayfasında belgeliyor ve Mac kurulum yönergelerini Install Swift — macOS sayfasında sunuyor.
Kurulum sayfasında belgelenen iş akışını kullanarak küçük bir yürütülebilir paket oluşturun:
mkdir TypedCalculations
cd TypedCalculations
swift package init --name TypedCalculations --type executable
Sources altındaki oluşturulan Swift giriş dosyasını aşağıdaki üç kod bloğuyla, tek bir dosyada ve belirtilen sırayla değiştirin. Yalnızca bir yürütülebilir giriş noktası bulundurun. Bu örnek, paketin olağan varsayılan aktör yalıtımını kullanır; varsayılan ana aktör yalıtımıyla yapılandırılmış bir projede hesaplama türleri ve işlevleri bu yalıtımın dışında tanımlanmalıdır.
Girdileri ve somut hataları tanımlayın
Hata enum'u yapılandırılmış bilgileri korur: durum sorunu, ilişkili indeks ise girdiyi belirtir. Foundation'ın LocalizedError protokolü, sunum için isteğe bağlı bir errorDescription sağlar. Aşağıdaki İngilizce iletiler örnek dizelerdir; protokole uymak bunları otomatik olarak çevirmez. Bkz. 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>
}
Hesaplayın, sonuçları toplayın ve türü belirli fırlatmaları geri kazanın
Aritmetik işlemde multipliedReportingOverflow(by:) kullanılır. Apple, partialValue değerinin yalnızca overflow false olduğunda çarpımın tamamını içerdiğini; aksi durumda kesilmiş bir çarpım içerdiğini belgeliyor. Aşağıdaki guard, bu kesilmiş değerin başarılı bir piksel sayısı olarak kullanılmasını önler. Bkz. 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
}
Result closure'ındaki açık throws(CalculationError) açıklaması, çıkarıma güvenmek yerine hata sözleşmesini görünür kılar. SE-0413, başlangıçta planlanan tam türü belirli fırlatma çıkarımı değişikliklerinin Swift 6.0'da uygulanmadığını belirtiyor. Bkz. Typed throws içindeki uygulama notu.
Yalnızca üst toplayıcı collected dizisine ekleme yapar. Her alt görev bir dikdörtgen ve indeks alıp kendi Sendable sonucunu döndürür. Bu, alt görev sonuç değerlerinin Sendable olmasını gerektiren görev grubu koşuluna uyar; fırlatmayan grup gövdesi bunları for await ile toplar. Bkz. withTaskGroup.
Yürütülebilir bir giriş noktası ekleyin
Bu son blok tüm örnek girdileri sağlar ve toplu işlem işlevini çağırır. catch içindeki açık atama, derleme zamanında hedeflenen API sözleşmesini de denetler: yakalanan değer bir CalculationError olmalıdır.
@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.")")
}
}
}
}
Yürütülebilir dosyayı paket dizininden çalıştırın:
swift run TypedCalculations
Derleme iletileri hariç beklenen uygulama çıktısı aşağıda gösterilmiştir. Araştırma ortamında Swift derleyicisi bulunmadığından program, alınan API belgelerine göre incelendi ancak orada derlenip çalıştırılmadı.
Valid: [307200, 64000]
Multiple failures: Rectangle 1 needs positive dimensions.
Overflow: Rectangle 1 has too many pixels for Int.
Empty: []
Geçerli sayılar örnek girdilerden doğrudan elde edilir: 640 ile 480'in çarpımı 307.200, 320 ile 200'ün çarpımı 64.000'dir. Birden fazla hatanın olduğu örnekte, ikinci alt görev daha önce tamamlansa bile sonuçlar girdi sırasına göre incelendiğinden ilk dikdörtgen seçilir.
İptal ve kısmi sonuçlar nasıl davranır?
Swift'te iptal eşgüdümlüdür. Bir görevin iptali gözlemleyip buna yanıt vermesi gerekir; iptal isteği hesaplamayı kendiliğinden kesmez. Yapılandırılmış görev grupları da dönmeden önce alt görevlerinin tamamlanmasını bekler. Swift dil kılavuzunun Concurrency bölümüne ve Apple'ın withTaskGroup belgelerine bakın.
Burada Task.isCancelled, gönderimden önce, her hesaplamanın içinde ve toplama işleminden sonra denetlenir. Bir kontrol noktası iptali algıladığında kod bunu mevcut .cancelled durumuyla gösterir. Bu Boolean değerini okumak, bu kapalı sözleşmeye başka bir fırlatılan hata türü eklenmesini önler. Apple bu özelliği Task.isCancelled sayfasında belgeliyor.
Toplama sonrası denetim, algılanan toplu işlem iptaline dikdörtgen hatalarından daha yüksek öncelik verir. Bu denetimler kontrol noktalarıdır; atomik bir garanti sağlamaz: son denetimden sonra istenen iptal döndürülen sonucu etkilemeyebilir. Tekrarlanan işler içeren daha uzun bir hesaplamada, iptalin daha erken algılanabilmesi için bu işlerin içine denetimler ekleyin.
Bu uygulamada başarısız bir alt görev iptali tetiklemez. Normal bir Result.failure değeri döndürür ve toplayıcı devam eder. Tüm alt görevler başarıyla tamamlanırsa çağıran taraf tüm sayıları alır. Değer açılırken bir hatayla karşılaşılırsa işlev hata fırlatır ve yerel olarak kısmen oluşturulmuş counts dizisi hiçbir zaman döndürülmez.
Masaüstü arayüzü bunun yerine her dikdörtgen için başarılı veya başarısız durumuna ihtiyaç duyuyorsa, genel toplu işlem sözleşmesini sıralı sonuçları döndürecek şekilde değiştirin. Her hatayı kendi indeksine bağlı tutun. Bu, burada uygulanmış ya hep ya hiç dizisinden farklı bir kullanıcıya dönük davranıştır.
Hataları gizlemeden kaybolan hata türünü teşhis edin
Uyarladığınız kod derlenmeyi durdurursa türü her sınırda şu sırayla izleyin:
- Hesaplamanın
throws(CalculationError)bildirdiğini denetleyin. - Alt görev değerinin, hata türü
any Errorolan bir sonuç yerineResult<Int, CalculationError>olduğunu denetleyin. - Çıkarım hata türünü genişletiyorsa closure'ın açık türü belirli fırlatma açıklamasını koruyun.
- Bu sonuç değerlerini taşımak için fırlatmayan
withTaskGroupkullanın. - Türü belirli toplu işlem işlevinin içinde
get()ile içteki değeri açın. - Eklenen fırlatmalı işlemlerin farklı bir hata türü getirip getirmediğini denetleyin.
Zorunlu tür dönüşümü bu sözleşmeyi kanıtlayamaz. Başarısız sonuçları atmak veya sıfırla değiştirmek de hesaplamanın anlamını değiştirir. Seçtiğiniz toplu işlem ilkesi bunları nasıl bildireceğine karar verene kadar hataları değer olarak koruyun.
Bu küçük çarpma işlemleri için eşzamanlılık, mimariyi gösteren bir örnektir; performans artışı iddiası yoktur. Elde edilen yararlı sonuç, kesin bir sözleşmedir: başarılı sayılar girdi sırasını korur, birden fazla hata için belirli bir seçim kuralı uygulanır ve çağıranlar doğrudan ele alabilecekleri somut bir hata enum'u alır.
Kaynaklar
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.