~/wiki

Worker Patterns

Confiance : high
worker-patternsconcurrencyserial-dispatch-queuebackground-processingthread-safetyswift-concurrencymain-actor-isolationperformance-optimizationreal-time-processing

Design patterns for managing background processing tasks that need to run off the main thread while maintaining thread safety and coordinated access to shared resources. Essential for real-time applications like audio processing where blocking the main thread would cause UI freezes.

Core Concepts

Serial Dispatch Queues: Create dedicated queues for specific processing tasks to ensure operations execute in order without blocking the main thread.

private let wakeWordQueue = DispatchQueue(label: "wake-word-inference", qos: .userInitiated)

Worker Class Separation: Extract heavy processing logic into dedicated worker classes that own their execution context and communicate back to the main thread via callbacks.

class WakeWordInferenceWorker {
    private let queue = DispatchQueue(label: "inference-worker")
    private let pipeline: OpenWakeWordPipeline
    
    func processAudio(_ samples: [Float]) {
        queue.async { [weak self] in
            // Heavy processing off main thread
            let result = self?.pipeline.process(samples)
            DispatchQueue.main.async {
                // Callback to main thread
                self?.delegate?.wakeWordDetected(result)
            }
        }
    }
}

Swift Concurrency Integration

Actor Isolation Management: With Swift's strict concurrency model, worker patterns help manage @MainActor isolation by clearly separating concerns.

@MainActor
class AudioController {
    nonisolated private let worker = WakeWordInferenceWorker()
    
    nonisolated func audioTap(buffer: AVAudioPCMBuffer) {
        // Can safely call worker from audio thread
        worker.processAudio(extractSamples(buffer))
    }
}

Quality of Service: Use appropriate QoS classes for worker queues based on processing urgency:

  • .userInitiated for real-time audio processing
  • .utility for background analysis
  • .background for non-urgent batch processing

Common Patterns

Audio Processing Worker

Handles continuous audio stream processing without blocking the main thread:

class AudioProcessingWorker {
    private let processingQueue = DispatchQueue(label: "audio-processing", qos: .userInitiated)
    private var audioBuffer: CircularBuffer<Float> = CircularBuffer(capacity: 16000)
    
    func enqueueAudio(_ samples: [Float]) {
        processingQueue.async { [weak self] in
            self?.audioBuffer.append(samples)
            self?.processBufferIfReady()
        }
    }
}

Rolling Buffer Management

Maintain sliding windows of data for streaming inference:

class RollingBufferWorker<T> {
    private var buffer: [T] = []
    private let maxSize: Int
    private let queue = DispatchQueue(label: "buffer-worker")
    
    func append(_ items: [T], completion: @escaping ([T]) -> Void) {
        queue.async { [weak self] in
            guard let self = self else { return }
            self.buffer.append(contentsOf: items)
            if self.buffer.count > self.maxSize {
                self.buffer.removeFirst(self.buffer.count - self.maxSize)
            }
            DispatchQueue.main.async {
                completion(Array(self.buffer))
            }
        }
    }
}

Best Practices

Weak Self: Always use [weak self] in async closures to prevent retain cycles.

Main Thread Callbacks: Process results on the main thread for UI updates, but do heavy lifting in worker queues.

Queue Naming: Use descriptive queue labels for debugging and profiling.

Error Handling: Propagate errors back to main thread through completion handlers or delegate methods.

Resource Cleanup: Ensure worker queues can be properly disposed of when parent objects are deallocated.

See Also