Skip to content

Concurrency architecture, isolation, cancellation, and lifecycle

Concurrency architecture answers two questions before tasks are launched: who owns each mutable value, and what lifetime ends the work?

Draw an isolation map

MainActor: visible editor, routes, presentation phase
SyncEngine actor: outbox pass, cursor, retry state
NoteStore actor: transaction and durable mutation ordering
Sendable values: notes, changes, DTOs, acknowledgements
Unisolated work: pure decoding, validation, and formatting
actor SyncEngine {
private var activeRun: Task<Void, Never>?
func start() {
guard activeRun == nil else { return }
activeRun = Task { [weak self] in
await self?.drainOutbox()
}
}
func stop() {
activeRun?.cancel()
activeRun = nil
}
}

The real drain loop checks cancellation before expensive work and between operations. Cancellation is a control signal, so cleanup and transaction rollback still run.

Tie work to a lifetime

A search task belongs to the current query. An editor save belongs to the save command and may outlive one render, but not necessarily the scene. Background synchronization belongs to the approved background task window. Store each task where its owner can cancel it.

Actors are ownership boundaries

An actor protects its isolated state. It is not merely a queue for arbitrary work, and making every service an actor can create unnecessary hops without clarifying ownership. Use detached tasks only when work truly has no inherited priority, actor context, task-local values, or structured parent lifetime.

Series navigation

References

  • Swift concurrency covers tasks, cancellation, actors, and sendable values.
  • MainActor defines the global actor used for UI state.