Gebruik voor een Mac-app die lokale asynchrone berekeningen in indieningsvolgorde moet afronden een synchrone indieningsmethode op de main actor die elke taak achter zijn voorganger koppelt. Actorisolatie alleen biedt die volgordegarantie niet.
Deze handleiding bouwt een kleine wachtrij voor synthetische integerberekeningen. Ze definieert precies wanneer indiening plaatsvindt, behoudt de volgorde over opschortingspunten heen, retourneert het resultaat van elke berekening en laat latere taken doorgaan nadat een eerdere taak mislukt.
Wat betekent strikte indieningsvolgorde?
Stel dat een desktopcalculator taken A, B en C indient. Het vereiste contract is: de berekening van A is klaar voordat de berekening van B begint, en B is klaar voordat C begint. Drie onafhankelijk gestarte taken in alfabetische volgorde afwachten, zou alleen de waarneming van hun resultaten ordenen.
De indieningsgrens is van belang. In deze implementatie vindt indiening plaats wanneer de op de main actor geïsoleerde methode submit wordt uitgevoerd en de volgende schakel in de keten instelt. Opeenvolgende synchrone aanroepen vanuit één main-actorcontext leggen de volgorde A, B, C vast zonder ertussen op te schorten.
Als onafhankelijke taken gelijktijdig om indiening vragen, is hun aankomstvolgorde niet gespecificeerd. De wachtrij behoudt de volgorde waarin de indieningsmethode ze accepteert. Als je toepassing een specifieke volgorde tussen die producenten vereist, leg die dan vast voordat je ze indient, bijvoorbeeld door een eindige batch in een expliciet geordende array te verzamelen.
Het ontwerp gebruikt de main actor voor de korte stap waarin de administratie wordt bijgewerkt. Apple definieert MainActor als een singletonactor waarvan de executor gelijkwaardig is aan de main dispatch queue. De daadwerkelijke berekening hieronder hoort thuis in een afzonderlijke workeractor. Documentatie over MainActor
Waarom volstaan een actor of een seriële operation queue niet?
Een actor voert actor-geïsoleerd werk één onderdeel tegelijk uit, maar een asynchrone methode kan worden opgeschort, waardoor een andere aanroep kan doorgaan. In het actorontwerp van Swift staat ook expliciet dat taken die op een actor wachten niet gegarandeerd worden uitgevoerd in de volgorde waarin ze oorspronkelijk erop wachtten. Daarom garandeert het onderbrengen van een volledige asynchrone berekening in een actor op zichzelf niet dat de ene berekening klaar is voordat de volgende begint. SE-0306: Actors
Afzonderlijke Task-instanties maken levert evenmin een afhankelijkheid tussen ze op. Taken kunnen na het aanmaken beginnen met uitvoeren; als je hun handles bewaart, kun je ze afwachten of annuleren, maar daarmee leg je geen uitvoeringsvolgorde vast. Documentatie over Task
Foundation biedt een ander mechanisme voor volgorde. OperationQueue plant op basis van gereedheid, prioriteit en afhankelijkheden; Apple adviseert afhankelijkheden te gebruiken wanneer een specifieke uitvoeringsvolgorde van belang is. maxConcurrentOperationCount instellen op 1 beperkt de gelijktijdigheid, maar voor de volgorde zijn nog steeds expliciete afhankelijkheden nodig. Documentatie over OperationQueue
Kies het mechanisme dat past bij wat je toepassing al nodig heeft:
| Situatie | Geschikte aanpak | Waardoor wordt de volgorde bepaald? |
|---|---|---|
| Eén bekende batch in één asynchrone functie | Een lus die elke berekening afwacht voordat ze verdergaat | Sequentiële control flow |
| Taken komen na verloop van tijd binnen via desktopacties | Een synchrone indieningsmethode met een taakketen | Elke berekening wacht op de voltooiingspoort van haar voorganger |
| Bestaande Foundation-bewerkingen | Afhankelijkheden configureren voordat ze aan de wachtrij worden toegevoegd | Elke bewerking hangt af van de voltooide status van de vorige bewerking |
De taakketen is hier nuttig omdat het indienen onmiddellijk een handle retourneert, terwijl de berekeningen later worden uitgevoerd. Voor een vaste batch zonder binnenkomende taken is de sequentiële lus meestal korter.
Bouw een wachtrij die volledige asynchrone berekeningen afwacht
De wachtrij bewaart een private, niet-falende voltooiingstaak genaamd tail. Elke indiening legt de huidige staart vast, maakt een berekeningstaak die daarop wacht en vervangt de staart door een nieuwe taak die de voltooiing van de berekening afwacht.
De voltooiingstaak wacht op result, dat staat voor succes of mislukking nadat een taak is voltooid. Zo kan een mislukte berekening de volgende taak vrijgeven zonder de fout kwijt te raken uit de eigen handle van de berekening. Aanroepers kunnen die fout nog steeds via de handle ophalen. Documentatie over taakresultaten
Hieronder staat een volledige opdrachtregel-demonstratie voor een Mac met een Swift 6-toolchain. Sla deze op als OrderedDemo.swift. De broncode is nagekeken, maar kon in deze onderzoeksomgeving niet worden gecompileerd omdat er geen Swift-compiler beschikbaar was.
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)")
}
}
Compileer en voer het opgeslagen programma vanuit de bijbehorende map uit in Terminal:
swiftc -swift-version 6 -parse-as-library OrderedDemo.swift -o OrderedDemo
./OrderedDemo
De verwachte uitvoer volgt uit de invoer en afhankelijkheidsketen van het voorbeeld:
start A
finish A
start B
finish B
start C
finish C
Answers: [9, 16, 25]
De asserties controleren zowel de geretourneerde waarden als de grenzen van de berekeningen. Alleen resultaten afdrukken terwijl je handles in indieningsvolgorde afwacht, garandeert niet dat de berekeningen in die volgorde zijn uitgevoerd.
De worker roept bewust Task.yield() aan, zodat de berekening de gelegenheid krijgt om te worden opgeschort. Yields geven andere taken de kans om uit te voeren, hoewel de executor dezelfde taak meteen kan hervatten. De volgorde in de wachtrij komt dus voort uit de afhankelijkheden, niet uit de aanname dat een yield tot een taakwissel leidt. Documentatie over Task yield
Waarom behoudt de keten de volgorde tijdens opschorting?
Drie details garanderen dit. Ten eerste bevat submit geen opschortingspunt, waardoor een andere indiening op de main actor niet tussendoor kan komen terwijl de methode tail vastlegt en vervangt. Swift documenteert dat actor-geïsoleerde code zonder opschortingspunten wordt uitgevoerd zonder dat een andere actortaak die reeks onderbreekt. Concurrency: Actors
Ten tweede wacht elke taak op de vastgelegde voorganger voordat de berekeningsclosure wordt aangeroepen. Toegang tot value van een niet-falende taak wacht totdat die taak is voltooid. De latere taak kan al vroeg de wachtende administratie uitvoeren, maar kan de berekening niet eerder starten. Documentatie over de value van een niet-falende Task
Ten derde wordt de voltooiingspoort van de voorganger pas voltooid nadat de eerdere taak een resultaat of fout oplevert. Door de schakels achterwaarts te volgen, ontstaat de volgorde voor elke geaccepteerde indiening: C wacht op de voltooiingspoort van B, die wacht op B, die wacht op de voltooiingspoort van A.
Dit is een volgordegarantie voor het werk waarop binnen elke ingediende closure wordt gewacht. Als een closure een andere, niet-gestructureerde taak start en terugkeert zonder die af te wachten, kan dat extra werk langer duren dan de closure zelf. Neem elke vereiste berekeningsstap op in de operatie waarop wordt gewacht voordat je terugkeert.
Houd voor desktopintegratie één wachtrij-instantie bij in de main-actorcontroller of het model dat de relevante acties afhandelt. Roep submit rechtstreeks vanuit die context aan en gebruik vervolgens de geretourneerde handle om de voltooiing te volgen. Als je eerst een afzonderlijke taak maakt en pas indient nadat die is begonnen, verschuift de indieningsgrens naar het moment waarop die taak uiteindelijk wordt uitgevoerd.
Hoe moeten fouten en annulering zich gedragen?
Deze implementatie hanteert een beleid waarbij na een fout wordt doorgegaan. Een mislukte taak behoudt haar fout, haar voltooiingspoort wordt voltooid en de volgende geaccepteerde taak mag beginnen met berekenen. De demonstratie bekijkt result van elke handle, in plaats van de resterende resultaten weg te laten na de eerste fout.
Wijzig voor een eenvoudige foutcontrole de invoer van B van 4 in 1001. De worker wijst de invoer af voordat hij vermenigvuldigt. Pas de verwachte antwoorden aan naar [9, 25] en het verwachte spoor naar start A, finish A, start B, start C, finish C. Het programma moet de fout afdrukken en C toch berekenen. De invoergrens is een illustratief contract dat is gekozen om deze rekenkundige bewerkingen klein te houden.
Annulering verloopt coöperatief. De wachtrij controleert op annulering nadat ze op haar voorganger heeft gewacht, en de worker controleert opnieuw na het yielden. Task.checkCancellation() gooit CancellationError als annulering is aangevraagd. Documentatie over Task checkCancellation
Controleer annulering vóór uitvoering door b.cancel() direct na het indienen van C aan te roepen en vóór de resultatenlus. Bij deze ononderbroken indieningen op de main actor kan B zijn berekening nog niet zijn begonnen. Pas de verwachte antwoorden aan naar [9, 25] en verwijder beide B-vermeldingen uit het verwachte spoor. B wacht nog steeds op de voltooiingspoort van A voordat het de annulering meldt; daarna gaat C verder.
Houd de voltooiingspoorten privé. Ze vormen de administratie van de keten, terwijl de geretourneerde handles staan voor afzonderlijke berekeningen. Een taak annuleren betekent niet dat een voorafgaande berekening moet stoppen of dat latere berekeningen moeten beginnen terwijl die nog bezig is.
Als je toepassing juist vereist dat de hele batch stopt na één fout, is daarvoor een expliciet batchbeleid nodig. Door de poort fouten te laten doorgeven, verandert welke latere taken worden uitgevoerd en hoe hun resultaten moeten worden gerapporteerd.
Wanneer gebruik je in plaats daarvan Foundation-afhankelijkheden?
Als de desktoptoepassing werk al voorstelt als Foundation-Operation-objecten, leg dan een afhankelijkheid vast van elke bewerking naar haar voorganger voordat je die aan de wachtrij toevoegt. Foundation beschouwt de afhankelijkheid als vervuld zodra de voorganger is voltooid, ongeacht of die is geslaagd of mislukt; gedrag dat afhankelijk is van succes vereist afzonderlijke afhandeling van de uitkomst. Documentatie over Operation
Wees voorzichtig wanneer je Swift-asynchrone functies naar dat model omzet. Een synchrone bewerking die een Task start en onmiddellijk terugkeert, vertegenwoordigt niet de volledige levensduur van die taak. Een aangepaste asynchrone bewerking moet de uitvoerings- en voltooiingsstatus nauwkeurig beheren, inclusief de vereiste meldingen, zodat afhankelijke bewerkingen wachten op het bedoelde werk. Apple beschrijft die verantwoordelijkheden onder asynchrone bewerkingen en rapportage van de voltooiingsstatus. Documentatie over de levenscyclus van Operation
Voor de lokale asynchrone calculator die hier is ontwikkeld, maakt de taakketen het contract duidelijk: indieningen synchroon accepteren, wachten op de voltooiing van de voorganger, de volledige berekening uitvoeren en de uitkomst beschikbaar maken. Behoud die grenzen wanneer je de wachtrij koppelt aan desktopacties en valideer de uitvoering aan de hand van start- en voltooiingssporen én geretourneerde waarden.
Bronnen
- MainActor — Apple
- SE-0306: Actors — Swift
- Task — Apple
- OperationQueue — Apple
- result — Apple
- yield() — Apple
- Concurrency — Swift
- value: Nonthrowing task — Apple
- checkCancellation() — Apple
- Operation — Apple