Om geslaagde Swift-berekeningen te behouden wanneer zustertaken mislukken, vang je de fout van elke berekening op binnen de onderliggende taak en retourneer je een resultaatwaarde. Dit voorbeeld voor de Mac-opdrachtregel verzamelt elk resultaat, behoudt de invoervolgorde en houdt geslaagde waarden beschikbaar.
De taak is een kleine, synthetische batchberekening: kwadrateer een lijst met niet-negatieve gehele getallen en rapporteer welke invoeren zijn geslaagd. Negatieve invoeren en integeroverloop veroorzaken afzonderlijke fouten. Alle berekeningsgegevens blijven in het geheugen; het programma drukt de resultaten af in Terminal.
Waarom kan één fout het verzamelen van resultaten onderbreken?
Een onderliggende taak die een fout gooit, annuleert zijn zustertaken niet onmiddellijk. De bepalende grens is de body van de taakgroep: wanneer daaruit een fout ontsnapt, annuleert Swift de resterende onderliggende taken. Apple documenteert dit onderscheid in de referentie voor withThrowingTaskGroup(of:returning:isolation:body:).
Een werklus die fouten gooit en een buitenliggende catch gebruikt, kan daardoor bij de eerste waargenomen fout stoppen met verzamelen. Eerdere waarden zijn mogelijk al opgeslagen, maar de lus wordt na die catch niet hervat. Resterende onderliggende taken worden geannuleerd als de fout uit de body van de groep ontsnapt.
Maak het foutbeleid expliciet voor onafhankelijke berekeningen. Het onderstaande voorbeeld gebruikt een niet-foutgooiende taakgroep waarvan de onderliggende taken succes, mislukking of annulering retourneren. De berekening zelf gooit nog steeds fouten; de wrapper zet die fout om in een waarde voordat deze de groep bereikt.
Dit past bij de gedocumenteerde grens van Swift: onderliggende taken in een niet-foutgooiende taakgroep kunnen gegooide fouten niet doorgeven, dus moeten ze die intern afhandelen of hun resultaat als retourwaarde weergeven. Zie Apple's TaskGroup-documentatie.
Bepaal wat elke invoer moet retourneren
Het batchcontract is één resultaat per invoerpositie. Een geslaagde berekening retourneert een exact kwadraat als geheel getal. Een afgewezen berekening retourneert een beschrijving van de fout. Een berekening die wordt overgeslagen omdat annulering is waargenomen, retourneert een annuleringsresultaat.
| Situatie | Resultaat | Batchgedrag |
|---|---|---|
Niet-negatieve invoer waarvan het kwadraat binnen het bereik van Int64 valt |
Succes met het kwadraat | Behoud de waarde |
| Negatieve invoer | Mislukking: negativeInput |
Blijf andere resultaten verzamelen |
Kwadraat overschrijdt het bereik van Int64 |
Mislukking: overflow |
Blijf andere resultaten verzamelen |
| Annulering waargenomen vóór de berekening | Geannuleerd | Behoud voltooide resultaten en sla openstaand werk over |
Het afwijzen van negatieve invoeren is een illustratieve toepassingsregel; negatieve getallen kunnen wiskundig worden gekwadrateerd. Door die regel gescheiden te houden van overloop, laat het voorbeeld twee verschillende foutcondities zien.
Gebruik voor vermenigvuldiging multipliedReportingOverflow(by:) en controleer de overloopvlag. Apple geeft aan dat de geretourneerde gedeeltelijke waarde alleen het volledige product is als die vlag false is; anders is de waarde afgekapt. Het voorbeeld negeert die afgekapt waarde en gooit een fout. Zie Int64.multipliedReportingOverflow(by:).
Elk resultaat bevat ook de index van de invoer. Swift garandeert niet in welke volgorde onderliggende taken worden voltooid, dus alleen voltooide waarden toevoegen kan de oorspronkelijke volgorde niet behouden. De taalgids beschrijft dit planningsgedrag onder Concurrency.
Voer het volledige voorbeeld uit op je Mac
Gebruik een Mac met Swift 6 of een latere toolchain. Voer in Terminal swift --version uit om de geïnstalleerde compiler te controleren, zoals aanbevolen in Swift.orgs Getting Started-gids. Als Swift niet beschikbaar is, volg dan de installatielink in die gids voordat je verdergaat.
Sla het volgende volledige programma in een werkmap op als Calculations.swift. Het importeert Foundation om een initiële gelijktijdigheidslimiet te kiezen op basis van het aantal actieve processors.
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)")
}
}
Compileer en voer het programma vanuit die map uit:
swiftc -swift-version 6 -parse-as-library Calculations.swift -o Calculations
./Calculations
Met de optie -parse-as-library kan dit ene bronbestand het expliciete ingangspunt @main gebruiken. Het uitvoerbare bestand voert de berekeningen uit zonder toepassingsgegevens te lezen of te schrijven. Swift was niet beschikbaar in de onderzoeksomgeving, dus het programma is daar wel bekeken maar niet gecompileerd of uitgevoerd.
Voor de opgegeven invoeren zonder annulering is de verwachte uitvoer:
[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]
De afgedrukte volgorde komt voort uit sorteren op invoerindex na het verzamelen. De uitvoeringsvolgorde kan variëren zonder dat deze weergave verandert. De array met geslaagde waarden wordt daarna samengesteld, zodat het volledige resultatenoverzicht beschikbaar blijft voor inspectie.
Hoe behoudt de verzamelaar elk geslaagd resultaat?
De bovenliggende taak beheert de array met resultaten. Elke onderliggende taak legt één index en één onveranderlijke invoer vast, berekent het resultaat en retourneert een rij. Onderliggende taken voegen nooit rechtstreeks waarden toe aan de array van de bovenliggende taak. Dit volgt de aanpak in de taalgids, waarbij resultaten van onderliggende taken binnen de body van de taakgroep worden verzameld. Zie Concurrency.
De verzamelaar dient aanvankelijk een beperkt aantal onderliggende taken in. Elke voltooide rij maakt één plek vrij voor een volgende invoer. Een mislukte berekening laat de batch dus op dezelfde manier doorgaan als een geslaagde: beide retourneren een gewone rij en het verzamelen gaat door.
Foundation's activeProcessorCount geeft het aantal actieve verwerkingskernen weer dat op de computer beschikbaar is. Het voorbeeld beperkt dat aantal tot vier als illustratieve planningskeuze. Die limiet is geen gemeten optimum en garandeert niet dat er vier berekeningen tegelijk worden uitgevoerd. Zie Apple's documentatie over activeProcessorCount.
De gelijktijdigheidslimiet begrenst het aantal openstaande onderliggende taken, terwijl de resultatenarray nog steeds groeit met het aantal invoeren. Dit is geschikt voor een kleine batch op een desktop. Voor deze kleine kwadraten laat het voorbeeld zien hoe resultaten worden afgehandeld; het doet geen uitspraak over betere prestaties dan een sequentiële lus.
De controle op lege invoer retourneert een lege array. Een gelijktijdigheidslimiet van nul of lager wordt op één gezet. Dubbele invoerwaarden blijven afzonderlijk, omdat hun identiteit afhangt van hun positie; twee gelijke invoeren leveren dus nog steeds twee rijen op.
Wat gebeurt er wanneer de bovenliggende taak wordt geannuleerd?
Annulering in Swift is coöperatief. Wanneer een bovenliggende taak wordt geannuleerd, wordt die annulering doorgegeven aan de gestructureerde onderliggende taken, maar een onderliggende taak moet dat signaal waarnemen en erop reageren. Apple's TaskGroup-documentatie beschrijft zowel het doorgeven als de vereiste van coöperatieve afhandeling.
Hier roept evaluate vóór de berekening Task.checkCancellation() aan. Apple documenteert dat deze methode CancellationError gooit wanneer annulering wordt waargenomen; de wrapper vangt die fout afzonderlijk op en retourneert een geannuleerde rij. Zie checkCancellation().
Zodra de verzamelaar annulering waarneemt, markeert die invoeren die nog niet zijn ingediend als geannuleerd. Reeds ingediende onderliggende taken worden nog steeds afgehandeld. Hun geslaagde resultaten blijven in het rapport staan en onderliggende taken die annulering waarnemen, retourneren geannuleerde rijen.
Annulering kan plaatsvinden nadat een onderliggende taak de controle heeft uitgevoerd. Die taak kan dan nog steeds succesvol afronden, wat volgens dit contract is toegestaan. Als de berekening een lange lus wordt, voeg dan op zinvolle intervallen annuleringscontroles toe binnen die berekening. De groep wacht nog steeds tot alle ingediende onderliggende taken zijn voltooid voordat deze wordt geretourneerd, zoals gedocumenteerd voor TaskGroup.
Wanneer moet je in plaats daarvan een foutgooiende taakgroep gebruiken?
Als bestaande onderliggende taken al fouten gooien en je die interface wilt behouden, kun je met een foutgooiende taakgroep ook afzonderlijke resultaten verzamelen. Roep nextResult() herhaaldelijk aan en schakel tussen elk geretourneerd succes of mislukking totdat het optionele resultaat leeg is. In tegenstelling tot next() retourneert deze methode een Result-waarde in plaats van de fout van de onderliggende taak naar de aanroeper te gooien. Apple documenteert het gedrag bij voltooiing in nextResult().
Neem voor dat ontwerp de invoeridentiteit op in geslaagde waarden en in eventuele fouten waaraan je de invoer wilt kunnen koppelen. Een kale rekenfout geeft niet aan welke van meerdere invoeren die heeft veroorzaakt. Kies ook een expliciet annuleringsbeleid in plaats van annulering als een gewone rekenfout te behandelen.
Gebruik de aanpak die resultaten retourneert wanneer een volledig rapport per invoer de belangrijkste vereiste is. Gebruik een foutgooiende groep wanneer het de gewenste batchafhandeling is dat een fout uit de groep wordt doorgegeven, of wanneer het handiger is om bestaande foutgooiende bewerkingen via nextResult() te verwerken.
Voor deze lokale rekentaak is het essentiële contract eenvoudig: behoud elke geslaagde waarde, leg elke mislukte invoer vast en blijf verzamelen. Door bij elk resultaat een invoerindex mee te geven, blijft het uiteindelijke rapport bruikbaar, zelfs wanneer berekeningen in een onvoorspelbare volgorde worden voltooid.
Bronnen
- withThrowingTaskGroup(of:returning:isolation:body:) — Apple Developer Documentation.
- TaskGroup — Apple Developer Documentation.
- Concurrency — The Swift Programming Language, Swift project.
- multipliedReportingOverflow(by:) — Apple Developer Documentation.
- Getting Started — Swift.org.
- activeProcessorCount — Apple Developer Documentation.
- checkCancellation() — Apple Developer Documentation.
- nextResult() — Apple Developer Documentation.