Behoud getypeerde fouten bij gelijktijdige berekeningen in Swift door vanuit elke onderliggende taak een getypeerde Result terug te geven en die na het verzamelen uit te pakken. Dit Mac-voorbeeld berekent synthetische pixelaantallen voor rechthoeken en behoudt daarbij de invoervolgorde, expliciete fouten en een batchcontract waarbij alles slaagt of er een fout wordt gerapporteerd.
Waarom getypeerde resultaten gebruiken op de grens van de taakgroep?
Stel dat je een grafische desktoppreview bouwt die pixelaantallen berekent voor meerdere rechthoeken. Elke berekening kan ongeldige afmetingen afwijzen of een integeroverloop melden. De aanroeper heeft een volledige geordende array of een specifieke berekeningsfout nodig, zodat deze elke ondersteunde fout expliciet kan afhandelen.
Swift introduceerde getypeerde throws in Swift 6.0. Een declaratie zoals throws(CalculationError) beperkt de waarden die de functie kan gooien tot dat fouttype. Dit is nuttig voor een klein, gesloten berekeningscontract waarvan de implementatie en aanroepers zich samen ontwikkelen. Zie SE-0413: Typed throws.
De grens van de concurrency-API is van belang. Apple’s gedocumenteerde signatuur van withThrowingTaskGroup geeft de body een ThrowingTaskGroup<ChildTaskResult, any Error>. Het generiek ogende groepstype biedt via die factory geen concrete domeinfoutparameter. Daarom zorgt het toevoegen van getypeerde throws aan alleen je berekening er niet voor dat door deze groeps-API heen overal een concreet fouttype wordt gebruikt. Zie withThrowingTaskGroup.
Een ondersteunde aanpak is withTaskGroup te gebruiken en elke onderliggende taak Result<Int, CalculationError> te laten teruggeven. De taak wordt normaal voltooid met een waarde die succes of mislukking vertegenwoordigt. Nadat die waarden zijn verzameld, roept de batchfunctie get() aan, waarvan de gedocumenteerde signatuur throws(Failure) is. Zo herstel je getypeerd gooien zonder runtimecast. Zie Apple’s documentatie over Result en get().
Leg het batchcontract vast voordat je concurrencycode schrijft
In dit voorbeeld bevat elke invoer een integerbreedte en -hoogte, gemeten in pixels. De berekening is het product daarvan. Alles is synthetisch en blijft in het geheugen; het programma rendert geen afbeelding en laadt of bewaart geen applicatiegegevens.
De volgende beleidsregels zijn ontwerpkeuzes voor dit voorbeeld. Ze maken zowel de geretourneerde waarden als de gekozen fout voorspelbaar:
| Situatie | Batchgedrag |
|---|---|
| Alle rechthoeken zijn geldig | Geef pixelaantallen terug in invoervolgorde |
| Een breedte of hoogte is nul of negatief | Meld die rechthoek als ongeldig |
Een product past niet in Int |
Meld een overloop voor die rechthoek |
| Meerdere rechthoeken mislukken | Meld de fout met de laagste invoerindex |
| Annulering wordt waargenomen bij een batchcontrolepunt | Meld .cancelled |
| De invoerarray is leeg | Geef een lege array terug, tenzij annulering wordt waargenomen |
Voor het selecteren van de laagste invoerindex moeten alle resultaten van onderliggende taken worden verzameld voordat een berekeningsfout wordt gekozen. Swift garandeert de uitvoeringsvolgorde van taken in een groep niet, en de voltooiingsvolgorde kan afwijken van de indieningsvolgorde. Door aan elk resultaat een index toe te voegen, kan de verzamelaar de bedoelde volgorde herstellen. Zie TaskGroup.
Dit beleid past bij een kleine previewbatch waarin herhaalbare feedback belangrijk is. Het wacht bewust op alle berekeningen. Een ontwerp dat de eerste fout meldt die tijdens de uitvoering wordt waargenomen, maakt een andere afweging en kan bij verschillende uitvoeringen een andere rechthoek selecteren.
Bouw een volledig lokaal voorbeeld op je Mac
Gebruik een geïnstalleerde Swift 6-toolchain of nieuwer. Controleer deze in Terminal met swift --version. Swift.org beschrijft die controle in Getting Started en geeft installatie-instructies voor de Mac in Install Swift — macOS.
Maak een klein uitvoerbaar pakket met de workflow die op de installatiepagina wordt beschreven:
mkdir TypedCalculations
cd TypedCalculations
swift package init --name TypedCalculations --type executable
Vervang het gegenereerde Swift-invoerbestand onder Sources door de volgende drie codeblokken, in deze volgorde, als één bestand. Zorg dat er maar één uitvoerbaar toegangspunt is. Dit voorbeeld gebruikt de gewone standaardactorisolatie van het pakket; in een project dat standaardisolatie op de main actor gebruikt, moeten de berekeningstypen en -functies buiten die isolatie worden geplaatst.
Definieer invoer en concrete fouten
De fouten-enum behoudt gestructureerde informatie: de case benoemt het probleem en de bijbehorende index identificeert de invoer. Het Foundation-protocol LocalizedError levert een optionele errorDescription voor weergave. De Engelse meldingen hieronder zijn voorbeeldteksten; conformeren aan het protocol vertaalt ze niet automatisch. Zie 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>
}
Bereken, verzamel en herstel getypeerd gooien
De rekenkundige bewerking gebruikt multipliedReportingOverflow(by:). Apple documenteert dat de partialValue ervan alleen het volledige product bevat wanneer overflow onwaar is; anders bevat die een afgekapt product. De controle hieronder voorkomt dat die afgekorte waarde als een geldig pixelaantal wordt gebruikt. Zie 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
}
De expliciete annotatie throws(CalculationError) op de Result-closure maakt het foutcontract zichtbaar, in plaats van te vertrouwen op inferentie van de closure. SE-0413 vermeldt dat de oorspronkelijk geplande wijzigingen voor volledige inferentie van typed throws niet zijn geïmplementeerd in Swift 6.0. Zie de implementatienotitie in Typed throws.
Alleen de bovenliggende verzamelaar voegt elementen toe aan collected. Elke onderliggende taak krijgt een rechthoek en een index en retourneert een eigen Sendable-resultaat. Dit voldoet aan de eis van de taakgroep dat resultaatwaarden van onderliggende taken Sendable zijn; de niet-gooiende body van de groep verzamelt ze met for await. Zie withTaskGroup.
Voeg een uitvoerbaar toegangspunt toe
Dit laatste blok levert alle voorbeeldinvoer en roept de batchfunctie aan. De expliciete toewijzing in catch controleert ook tijdens het compileren het beoogde API-contract: de opgevangen waarde moet een CalculationError zijn.
@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.")")
}
}
}
}
Voer het uitvoerbare bestand uit vanuit de pakketmap:
swift run TypedCalculations
De verwachte uitvoer van de applicatie, zonder buildmeldingen, staat hieronder. In de onderzoeksomgeving was geen Swift-compiler beschikbaar. Daarom is dit programma gecontroleerd aan de hand van de opgehaalde API-documentatie, maar daar niet gecompileerd of uitgevoerd.
Valid: [307200, 64000]
Multiple failures: Rectangle 1 needs positive dimensions.
Overflow: Rectangle 1 has too many pixels for Int.
Empty: []
De geldige aantallen volgen rechtstreeks uit de voorbeeldinvoer: 640 vermenigvuldigd met 480 is 307.200 en 320 vermenigvuldigd met 200 is 64.000. In het voorbeeld met meerdere fouten wint de eerste rechthoek, omdat resultaten in invoervolgorde worden gecontroleerd, zelfs als de tweede onderliggende taak eerder is voltooid.
Hoe gedragen annulering en gedeeltelijke resultaten zich?
Annulering werkt in Swift coöperatief. Een taak moet annulering waarnemen en erop reageren; een annuleringsverzoek onderbreekt de berekening niet automatisch. Gestructureerde taakgroepen wachten ook tot hun onderliggende taken klaar zijn voordat ze terugkeren. Zie het hoofdstuk Concurrency in de taalhandleiding van Swift en Apple’s documentatie over withTaskGroup.
Hier wordt Task.isCancelled gecontroleerd vóór het indienen, binnen elke berekening en na het verzamelen. Wanneer een controlepunt annulering waarneemt, vertegenwoordigt de code die met de bestaande case .cancelled. Het uitlezen van de booleaanse waarde voorkomt dat er een ander fouttype wordt geïntroduceerd in dit gesloten contract. Apple documenteert deze eigenschap in Task.isCancelled.
De controle na het verzamelen geeft waargenomen batchannulering voorrang op fouten van rechthoeken. Deze controles zijn controlepunten, geen atomaire garantie: annulering die na de laatste controle wordt aangevraagd, heeft mogelijk geen invloed op het geretourneerde resultaat. Voeg bij een langere berekening met herhaald werk controles binnen dat werk toe, zodat annulering eerder kan worden waargenomen.
Een mislukte onderliggende taak leidt in deze implementatie niet tot annulering. De taak retourneert een normale Result.failure-waarde en de verzamelaar gaat verder. Als elke onderliggende taak slaagt, ontvangt de aanroeper alle aantallen. Als het uitpakken een fout aantreft, gooit de functie die en wordt de lokale, gedeeltelijk opgebouwde array counts nooit geretourneerd.
Als de desktopinterface in plaats daarvan voor elke rechthoek een geslaagde of mislukte status nodig heeft, wijzig dan het openbare batchcontract zodat het de geordende resultaten retourneert. Koppel elke fout aan de bijbehorende index. Dat geeft de lezer een ander gedrag dan de array met alles-of-fout die hier is geïmplementeerd.
Een verloren fouttype opsporen zonder fouten te verbergen
Als je aangepaste code niet meer compileert, volg dan het type bij elke grens in deze volgorde:
- Controleer of de berekening
throws(CalculationError)declareert. - Controleer of de waarde van de onderliggende taak
Result<Int, CalculationError>is en geen resultaat waarvan de foutany Erroris. - Behoud de expliciete annotatie voor getypeerd gooien van de closure als inferentie het fouttype verbreedt.
- Gebruik niet-gooiend
withTaskGroupom deze resultaatwaarden te transporteren. - Pak de waarde uit met
get()binnen de getypeerde batchfunctie. - Controleer of een nieuw toegevoegde gooiende bewerking een ander fouttype introduceert.
Een geforceerde cast kan dit contract niet bewijzen. Ook zou het negeren van mislukte resultaten of het invullen van nul de betekenis van de berekening veranderen. Behoud fouten als waarden totdat het gekozen batchbeleid bepaalt hoe ze worden gerapporteerd.
Voor deze kleine vermenigvuldigingen is concurrency een architecturale demonstratie; er wordt geen prestatieverbetering geclaimd. Het resultaat is een precies contract: geslaagde aantallen behouden de invoervolgorde, meerdere fouten hebben een deterministische selectieregel en aanroepers ontvangen een concrete fouten-enum die ze rechtstreeks kunnen afhandelen.
Bronnen
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.