Section 5/161 menit
5. Custom Executor
5. Custom Executor
Custom Executor memungkinkan Anda menentukan di mana dan bagaimana code actor berjalan. Berguna untuk integrasi dengan event loop eksternal, main queue milik framework, atau threading model khusus.
SerialExecutor Protocol
swift
// MARK: - Custom Serial Executor
// Executor berbasis DispatchQueue (berguna untuk integrasi legacy code)
final class DispatchQueueExecutor: SerialExecutor, @unchecked Sendable {
private let queue: DispatchQueue
init(queue: DispatchQueue) {
self.queue = queue
}
// Jadwalkan job untuk dieksekusi di queue ini
func enqueue(_ job: consuming ExecutorJob) {
// Job harus di-consume di thread yang benar
let unownedJob = UnownedJob(job)
queue.async {
unownedJob.runSynchronously(on: self.asUnownedSerialExecutor())
}
}
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
}
// Actor yang menggunakan custom executor
actor AudioProcessor {
private let audioQueue = DispatchQueue(label: "audio.processing", qos: .userInteractive)
private lazy var executor = DispatchQueueExecutor(queue: audioQueue)
// Ini yang menghubungkan actor dengan executor
nonisolated var unownedExecutor: UnownedSerialExecutor {
executor.asUnownedSerialExecutor()
}
func processAudioBuffer(_ buffer: AudioBuffer) {
// Selalu berjalan di audioQueue
applyEffects(to: buffer)
renderOutput(buffer)
}
private func applyEffects(to buffer: AudioBuffer) { /* ... */ }
private func renderOutput(_ buffer: AudioBuffer) { /* ... */ }
}
struct AudioBuffer {}
Executor untuk SwiftNIO Event Loop
swift
// MARK: - NIO EventLoop Executor
import NIO // hypothetical import
// Membungkus NIO EventLoop sebagai Swift Executor
final class NIOEventLoopExecutor: SerialExecutor, @unchecked Sendable {
private let eventLoop: EventLoop
init(_ eventLoop: EventLoop) {
self.eventLoop = eventLoop
}
func enqueue(_ job: consuming ExecutorJob) {
let unownedJob = UnownedJob(job)
eventLoop.execute {
unownedJob.runSynchronously(on: self.asUnownedSerialExecutor())
}
}
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
}
// Actor yang berjalan di NIO event loop
actor NIOChannel {
private let executor: NIOEventLoopExecutor
nonisolated var unownedExecutor: UnownedSerialExecutor {
executor.asUnownedSerialExecutor()
}
init(eventLoop: EventLoop) {
self.executor = NIOEventLoopExecutor(eventLoop)
}
// Code ini dijamin berjalan di NIO event loop — tidak perlu eventLoop.execute {}
func write(_ data: Data) async throws {
try await channel.writeAndFlush(data)
}
}
// Placeholder
protocol EventLoop { func execute(_ work: @escaping () -> Void) }
protocol Channel { func writeAndFlush(_ data: Data) async throws }
var channel: Channel { fatalError() }
TaskExecutor: Mengontrol Thread Pool (Swift 6.0+)
TaskExecutor (Swift 6.0) adalah executor yang bisa di-assign ke seluruh task tree, bukan hanya satu actor:
swift
// TaskExecutor: custom thread pool untuk task tree
final class BoundedThreadPoolExecutor: TaskExecutor, @unchecked Sendable {
private let threadCount: Int
private var threads: [Thread] = []
private let jobQueue = DispatchQueue(label: "bounded.pool", attributes: .concurrent)
private let semaphore: DispatchSemaphore
init(threadCount: Int) {
self.threadCount = threadCount
self.semaphore = DispatchSemaphore(value: threadCount)
}
func enqueue(_ job: consuming ExecutorJob) {
let unownedJob = UnownedJob(job)
let executor = asUnownedTaskExecutor()
semaphore.wait()
jobQueue.async { [weak self] in
defer { self?.semaphore.signal() }
unownedJob.runSynchronously(on: executor)
}
}
func asUnownedTaskExecutor() -> UnownedTaskExecutor {
UnownedTaskExecutor(ordinary: self)
}
}
// Gunakan custom executor untuk task tree
let pool = BoundedThreadPoolExecutor(threadCount: 4)
Task(executorPreference: pool) {
// Seluruh task tree ini — termasuk child tasks — berjalan di pool
async let a = heavyComputation1()
async let b = heavyComputation2()
let (r1, r2) = await (a, b)
process(r1, r2)
}
func heavyComputation1() async -> Int { 42 }
func heavyComputation2() async -> Int { 43 }
func process(_ a: Int, _ b: Int) {}