Kardeş görevler başarısız olduğunda Swift’teki başarılı hesaplamaları korumak için her hesaplamanın hatasını alt görevin içinde yakalayın ve bir sonuç değeri döndürün. Mac komut satırındaki bu örnek her sonucu toplar, giriş sırasını korur ve başarılı değerleri kullanılabilir durumda tutar.

Görev, küçük ve sentetik bir toplu hesaplamadır: negatif olmayan tam sayılardan oluşan bir listenin karelerini alın ve hangi girişlerin başarılı olduğunu bildirin. Negatif girişler ve tam sayı taşması ayrı ayrı hatalara yol açar. Tüm hesaplama verileri bellekte kalır; program sonuçlarını Terminal’de yazdırır.

Tek bir hata sonuç toplamayı neden kesebilir?

Hata fırlatan bir alt görev, kardeş görevlerini hemen iptal etmez. Sonucu belirleyen sınır, görev grubu gövdesidir: bir hata bu gövdeden dışarı çıktığında Swift kalan alt görevleri iptal eder. Apple bu ayrımı withThrowingTaskGroup(of:returning:isolation:body:) başvurusunda açıklar.

Bu nedenle, dışarıda bir catch bulunan ve hata fırlatan bir yineleme döngüsü, gözlemlenen ilk hatada toplamayı durdurabilir. Önceki değerler zaten saklanmış olabilir, ancak döngü bu catch’ten sonra devam etmez. Hata grup gövdesinden dışarı çıkarsa kalan alt görevler iptal sinyali alır.

Bağımsız hesaplamalar için hata politikasını açıkça belirleyin. Aşağıdaki örnek, alt görevlerin başarı, hata veya iptal sonucu döndürdüğü hata fırlatmayan bir görev grubu kullanır. Hesaplamanın kendisi yine hata fırlatır; sarmalayıcısı, hata gruba ulaşmadan önce onu bir değere dönüştürür.

Bu, Swift’in belgelenmiş sınırına uygundur: hata fırlatmayan bir görev grubundaki alt görevler fırlatılan hataları iletemez; bu nedenle hataları kendi içlerinde ele almalı veya sonucu bir dönüş değeri olarak temsil etmelidir. Apple’ın TaskGroup belgelerine bakın.

Her girişin ne döndürmesi gerektiğini tanımlayın

Toplu işlem sözleşmesi, her giriş konumu için bir sonuç üretir. Başarılı bir hesaplama tam sayı cinsinden kesin kareyi döndürür. Reddedilen bir hesaplama hata açıklaması döndürür. İptal gözlemlendiği için atlanan bir hesaplama, iptal sonucu döndürür.

Durum Sonuç Toplu işlem davranışı
Karesi Int64 aralığına sığan negatif olmayan giriş Kareyi içeren başarı Değeri koru
Negatif giriş Hata: negativeInput Diğer sonuçları toplamaya devam et
Kare Int64 aralığını aşar Hata: overflow Diğer sonuçları toplamaya devam et
Hesaplamadan önce iptal gözlemlendi İptal edildi Tamamlanan sonuçları koru ve bekleyen işleri atla

Negatif girişleri reddetmek açıklayıcı bir uygulama kuralıdır; negatif sayılar matematiksel olarak kareye yükseltilebilir. Bu kuralı taşmadan ayrı tutmak, örneğin iki farklı hata koşulunu göstermesini sağlar.

Çarpma için multipliedReportingOverflow(by:) kullanın ve taşma bayrağını inceleyin. Apple, döndürülen kısmi değerin yalnızca bu bayrak false olduğunda tam çarpım olduğunu belirtir; aksi durumda değer kesilmiştir. Örnek, kesilmiş bu değeri atar ve hata fırlatır. Int64.multipliedReportingOverflow(by:) sayfasına bakın.

Her sonuç ayrıca giriş indeksini taşır. Swift, alt görevlerin tamamlanma sırasını garanti etmediğinden, tamamlanan değerleri tek başına eklemek özgün sırayı koruyamaz. Dil kılavuzu bu zamanlama davranışını Concurrency bölümünde açıklar.

Mac’inizde tam örneği çalıştırın

Swift 6 veya sonraki bir araç zincirine sahip Mac kullanın. Yüklü derleyiciyi kontrol etmek için Swift.org’un Başlangıç kılavuzunda önerildiği gibi Terminal’de swift --version komutunu çalıştırın. Swift yüklü değilse devam etmeden önce bu kılavuzdaki kurulum bağlantısını izleyin.

Aşağıdaki tam programı çalışma klasörünüzde Calculations.swift adıyla kaydedin. Etkin işlemci sayısına göre başlangıç eşzamanlılık sınırını seçmek için Foundation’ı içe aktarır.

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)")
    }
}

Bu klasörden programı derleyip çalıştırın:

swiftc -swift-version 6 -parse-as-library Calculations.swift -o Calculations
./Calculations

-parse-as-library seçeneği, bu tek kaynak dosyanın açık @main giriş noktasını kullanmasını sağlar. Yürütülebilir dosya, uygulama verilerini okumadan veya yazmadan hesaplamaları yapar. Araştırma ortamında Swift kullanılamadığından program incelendi, ancak orada derlenmedi veya yürütülmedi.

İptal olmadan verilen girişler için beklenen çıktı şöyledir:

[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]

Yazdırılan sıra, toplama işleminden sonra giriş indeksine göre sıralamadan gelir. Bu sunum değişmeden yürütme sırası değişebilir. Başarılı değerler dizisi sonradan oluşturulur; böylece sonuçların tamamı incelenebilir durumda kalır.

Toplayıcı her başarılı sonucu nasıl korur?

Üst görev sonuçlar dizisinin sahibidir. Her alt görev bir indeks ve değişmez bir girdi yakalar, sonucunu hesaplar ve bir satır döndürür. Alt görevler doğrudan üst görevin dizisine ekleme yapmaz. Bu, dil kılavuzunun alt görev sonuçlarını görev grubu gövdesinde biriktirme yaklaşımına uygundur. Concurrency bölümüne bakın.

Toplayıcı başlangıçta sınırlı sayıda alt görev gönderir. Tamamlanan her satır, başka bir giriş için bir yer açar. Bu nedenle başarısız bir hesaplama da toplu işlemi başarılı bir hesaplama gibi ilerletir: ikisi de sıradan bir satır döndürür ve toplama sürer.

Foundation’daki activeProcessorCount, bilgisayarda kullanılabilir etkin işlemci çekirdeği sayısını bildirir. Örnek, açıklayıcı bir zamanlama tercihi olarak bu sayıyı dörtle sınırlar. Bu sınır, ölçülmüş bir optimum değildir ve dört hesaplamanın aynı anda çalışacağını garanti etmez. Apple’ın activeProcessorCount belgelerine bakın.

Eşzamanlılık sınırı bekleyen alt görevlerin sayısını belirlerken sonuçlar dizisi giriş sayısıyla birlikte büyümeye devam eder. Bu yaklaşım küçük bir masaüstü toplu işlemi için uygundur. Bu küçük kareler için örnek, sonuçların nasıl ele alınacağını gösterir; sıralı bir döngüye göre performans iddiasında bulunmaz.

Boş giriş denetimi boş bir dizi döndürür. Sıfır veya negatif eşzamanlılık sınırı bire yükseltilir. Kimlik konumdan geldiği için yinelenen giriş değerleri ayrı tutulur; böylece iki eşit giriş yine de iki satır üretir.

Üst görev iptal edildiğinde ne olur?

Swift’te iptal işbirliğine dayalıdır. Bir üst görev iptal edildiğinde iptal alt görevlerine yayılır, ancak alt görev bu sinyali gözlemleyip yanıt vermelidir. Apple’ın TaskGroup belgeleri hem yayılımı hem de işbirliğine dayalı ele alma gerekliliğini açıklar.

Burada evaluate, hesaplama yapmadan önce Task.checkCancellation() çağrısını yapar. Apple, iptal gözlemlendiğinde bu yöntemin CancellationError fırlattığını belgeliyor; sarmalayıcı bunu ayrı olarak yakalayıp iptal edilmiş bir satır döndürür. checkCancellation() sayfasına bakın.

Toplayıcı iptali gözlemlediğinde henüz gönderilmemiş girişleri iptal edilmiş olarak işaretler. Gönderilmiş alt görevler tamamlanana kadar izlenmeye devam edilir. Başarılı sonuçları raporda korunur ve iptali gözlemleyen alt görevler iptal edilmiş satırlar döndürür.

İptal, bir alt görevin denetiminden sonra gelebilir. Bu alt görev yine de başarıyla tamamlanabilir ve bu sözleşme bunu kabul eder. Hesaplama uzun bir döngüye dönüşürse, bu hesaplamanın içinde uygun aralıklarla iptal denetimleri ekleyin. TaskGroup için belgelerde açıklandığı üzere grup, dönmeden önce gönderilmiş tüm alt görevlerin bitmesini bekler.

Bunun yerine ne zaman hata fırlatan görev grubu kullanmalısınız?

Mevcut alt görevler zaten hata fırlatıyorsa ve bu arayüzü korumak istiyorsanız, hata fırlatan görev grubu da tek tek sonuçları toplamayı destekler. nextResult() çağrısını tekrarlayın ve isteğe bağlı sonuç boş olana kadar döndürülen her başarı veya hata üzerinde switch kullanın. next() yönteminden farklı olarak bu yöntem, alt görevin hatasını çağırana fırlatmak yerine bir Result değeri döndürür. Apple, tamamlanma sırası davranışını nextResult() sayfasında açıklar.

Bu tasarımda, başarılı değerlere ve ilişkilendirilmesi gereken hata değerlerine giriş kimliğini ekleyin. Tek başına bir aritmetik hatası, birkaç girişten hangisinin bu hatayı ürettiğini belirtmez. Ayrıca iptali sıradan bir hesaplama hatası olarak ele almak yerine açık bir iptal politikası seçin.

Giriş başına eksiksiz bir rapor temel gereksinimse sonuç döndüren yaklaşımı kullanın. Bir hatanın grup dışına iletilmesi, toplu işlemin istenen davranışıysa veya mevcut hata fırlatan işlemleri nextResult() üzerinden kullanmak daha uygunsa hata fırlatan grup kullanın.

Bu yerel hesaplama görevi için temel sözleşme basittir: başarılı her değeri koruyun, başarısız her girişi kaydedin ve toplamaya devam edin. Her sonuca giriş indeksini eklemek, hesaplamalar öngörülemez bir sırada tamamlansa bile nihai raporu kullanışlı kılar.

Kaynaklar